Графика для Windows средствами DirectDraw

         

Класс SuperSwitchWin


Поскольку программа SuperSwitch является видоизмененной версией Switch, мы не будем обсуждать весь ее код. Вместо этого будут рассмотрены лишь отличающиеся фрагменты SuperSwitch.


Рис. 4.2. Программа SuperSwitch

Отличия начинаются с того, что классы в этой программе называются SuperSwitchWin и SuperSwitchApp (вместо SwitchWin и SwitchApp). Класс SuperSwitchWin похож на SwitchWin, но в нем имеется несколько новых функций и переменных. Давайте посмотрим, что же изменилось. Объявление класса SuperSwitchWin приведено в листинге 4.6.

Листинг 4.6. Объявление класса SuperSwitchWin


class SuperSwitchWin : public DirectDrawWin { public: SuperSwitchWin(); protected: //{{AFX_MSG(SuperSwitchWin) afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: int SelectDriver(); int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces(); static HRESULT WINAPI StoreModeInfo(LPDDSURFACEDESC, LPVOID); void DrawScene(); void RestoreSurfaces(); BOOL CreateModeMenuSurface(); BOOL UpdateModeMenuSurface(); BOOL CreateRateMenuSurface(); BOOL UpdateRateMenuSurface(); BOOL CreateFPSSurface(); BOOL UpdateFPSSurface(); private: LPDIRECTDRAWSURFACE bmpsurf; int x,y; int xinc, yinc;

LPDIRECTDRAWSURFACE modemenusurf; int selectmode;

LPDIRECTDRAWSURFACE ratemenusurf; int selectrate; int numrates; BOOL ratemenu_up;

LPDIRECTDRAWSURFACE fpssurf; RECT fpsrect; BOOL displayfps; DWORD framecount;

BOOL include_refresh; CArray<DWORD,DWORD> refresh_rates[MAXDISPLAYMODES]; HFONT smallfont, largefont; };


Отличия начинаются с функции OnCreate(). Мы переопределяем функцию DirectDrawWin::OnCreate() так, чтобы перед инициализацией DirectDraw в ней выводилось диалоговое окно (в котором можно отключить изменение частоты смены кадров).

Другая новая функция - StoreModeInfo(). Эта функция косвенного вызова вызывается при составлении списка частот каждого видеорежима. Как говорилось в главе 3, класс DirectDrawWin имеет для этой цели собственную функцию косвенного вызова (DisplayModeAvailable()).
Вместо того чтобы изменять класс DirectDrawWin, мы воспользуемся функцией StoreModeInfo(), приспособленной для целей конкретного приложения. Это означает, что список видеорежимов будет составляться дважды: сначала без частот смены кадров (класс DirectDrawWin), а потом с частотами (класс SuperSwitchWin).

Далее в списке идут четыре новые функции:

CreateModeMenuSurface()

UpdateModeMenuSurface()

CreateRateMenuSurface()

UpdateRateMenuSurface()

Функции CreateModeMenuSurface() и UpdateModeMenuSurface() — это просто переименованные функции CreateMenuSurface() и UpdateMenuSurface() из программы Switch. Их пришлось переименовать, потому что теперь существуют две поверхности меню: одна — для видеорежимов, а другая — для частот смены кадров. Функции CreateModeMenuSurface() и UpdateModeMenuSurface() работают с поверхностью меню видеорежимов. Две новые функции, CreateRateMenuSurface() и UpdateRateMenuSurface(), предназначены для работы с поверхностью меню частот.



Теперь давайте рассмотрим новые и изменившиеся переменные класса. Указатель menusurf из программы Switch был переименован в modemenusurf по той же причине, по которой были переименованы функции для работы с поверхностью меню видеорежимов. Далее в классе появились шесть новых переменных. Я снова приведу объявления новых переменных класса из листинга 4.6:

LPDIRECTDRAWSURFACE ratemenusurf; int selectrate; int numrates; BOOL ratemenu_up; BOOL include_refresh; CArray<DWORD,DWORD> refresh_rates[MAXDISPLAYMODES];


Переменная ratemenusurf представляет собой указатель на интерфейс DirectDrawSurface и используется для работы с поверхностью меню частот. В целых переменных selectrate и numrates хранятся соответственно текущая выделенная частота и общее количество отображаемых частот. Логическая переменная ratemenu_up показывает, отображается ли меню частот в данный момент.

Значение логической переменной include_refresh определяется выбором пользователя, сделанным в окне диалога при старте программы. Если эта переменная равна TRUE, программа создает и выводит меню со списком частот для каждого выделенного видеорежима.Если переменная равна FALSE, частоты не отображаются. Наконец, массив refresh_rates предназначен для хранения возможных частот каждого видеорежима. Содержимое массива определяется с помощью косвенно вызываемой функции StoreModeInfo() и используется функцией UpdateRateMenusurface().


Класс SwitchWin


Давайте рассмотрим код программы Switch. Начнем с определения класса SwitchWin (см. листинг 4.2).

Листинг 4.2. Объявление класса SwitchWin


class SwitchWin : public DirectDrawWin { public: SwitchWin(); protected: //{{AFX_MSG(SwitchWin) afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: int SelectDriver(); int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces(); void DrawScene(); void RestoreSurfaces();

BOOL CreateMenuSurface(); BOOL UpdateMenuSurface(); BOOL CreateFPSSurface(); BOOL UpdateFPSSurface(); private: LPDIRECTDRAWSURFACE bmpsurf; int x, y; int xinc, yinc;

LPDIRECTDRAWSURFACE menusurf; int selectmode;

LPDIRECTDRAWSURFACE fpssurf; RECT fpsrect; BOOL displayfps; DWORD framecount;

HFONT smallfont, largefont; };


Класс SwitchWin содержит всего одну открытую (public) функцию — конструктор класса (вскоре мы его рассмотрим). В классе также присутствует функция OnKeyDown() — обработчик сообщений, созданный ClassWizard (закомментированные директивы AFX, окружающие функцию OnKeyDown(), используются ClassWizard для поиска функций-обработчиков). Мы воспользуемся этой функцией для обработки нажимаемых клавиш — стрелок, Enter и незаменимой клавиши Escape.

Следующие пять функций являются переопределенными версиями функций DirectDrawWin:

SelectDriver()

SelectInitialDisplayMode()

CreateCustomSurfaces()

DrawScene()

RestoreSurfaces()

С помощью функции SelectDriver() приложение выбирает используемое видеоустройство (если их несколько). Она полностью совпадает со стандартной версией, создаваемой AppWizard, и выводит меню при наличии нескольких драйверов. Функция SelectInitialDisplayMode() задает исходный видеорежим, устанавливаемый приложением. Здесь снова используется стандартная версия AppWizard, которая ищет видеорежим с параметрами 640x480x16.

Функция CreateCustomSurfaces() вызывается DirectDrawWin при активизации нового видеорежима; мы воспользуемся этой функцией для создания и подготовки поверхностей программы Switch.
Функция DrawScene() отвечает за обновление экрана; она будет использоваться для отображения анимации, меню видеорежимов и значения FPS. Наконец, функция RestoreSurfaces() вызывается классом DirectDrawWin при необходимости восстановить потерянные поверхности. Эта функция восстанавливает не только сами поверхности, но и (для особо важных поверхностей) их содержимое.

Затем класс SwitchWin объявляет четыре функции, специфические для программы Switch:

CreateMenuSurface()

UpdateMenuSurface()

CreateFPSSurface()

UpdateFPSSurface()


Это вспомогательные функции для работы с поверхностями, используемыми для вывода меню видеорежимов и значения FPS. Они подробно рассматриваются ниже в этой главе.

Закрытые переменные, объявленные в конце, предназначены для отображения анимации, меню видеорежимов и FPS, а также для работы со шрифтами средствами Win32.

Переменная bmpsurf - указатель на интерфейс DirectDrawSurface, через который мы будем обращаться к данным перемещаемого растра, а переменные x, y, xinc и yinc определяют его положение.

Указатель menusurf используется для доступа к поверхности меню видеорежимов, а в целой переменной selectmode хранится индекс текущего активного видеорежима.

Следующие переменные списка связаны с выводом значения FPS. Переменная fpssurf — указатель на интерфейс DirectDrawSurface, через который производится доступ к поверхности FPS. Структура типа RECT (fpsrect) содержит размеры поверхности fpssurf. Логическая переменная displayfps управляет отображением значения FPS, а в переменной framecount хранится количество кадров, выведенных в очередном временном интервале измерения FPS.

Две последние переменные, smallfont и largefont, имеют тип HFONT. Это логические номера шрифтов Win32, используемые для вывода текста на поверхностях menusurf и fpssurf.




Классы потоков в MFC


Для многопоточного программирования Windows можно выбирать между классами MFC и потоковыми функциями Win32. Microsoft рекомендует использовать в MFC-приложениях классы потоков. Для работы с потоками в MFC предусмотрены следующие классы:

CWinThread

CSyncObject

CEvent

CCriticalSection

CMutex

CSemaphore

CSingleLock

CMultiLock

Класс CWinThread представляет отдельный поток. Он присутствует во всех приложениях, потому что класс CWinApp (базовый для класса DirectDrawApp) является производным от CWinThread. Этот экземпляр класса CWinThread представляет основной поток приложения; чтобы добавить новые рабочие потоки, следует создать объекты CWinThread.

Класс CSyncObject

является виртуальным. Непосредственное создание экземпляров этого класса не разрешается; он существует лишь для того, чтобы обеспечивать функциональные возможности производных классов. Класс CSyncObject является базовым для классов CEvent, CCriticalSection, CMutex и CSemaphore. Объекты синхронизации, представленные этими классами, рассматривались в предыдущем разделе.

Классы CSingleLock и CMultiLock применяются для блокировки потоков по состоянию одного или нескольких событий. Класс CSingleLock блокирует поток до установки конкретного события, а CMultiLock— до установки одного или всех событий из заданного набора.

Позднее в этой главе мы воспользуемся классами CWinThread, CEvent, CCriticalSSection и CMultiLock.



Комбинированные приложения


Комбинированным называется приложение, которое может работать как в полноэкранном, так и в оконном режимах. Обычно комбинированные приложения довольно сложно устроены, но зато они предоставляют разумный компромисс. Пользователь может запустить приложение в оконном режиме, а если оно будет работать слишком медленно — перейти в полноэкранный режим.

Оптимальный подход к созданию комбинированного приложения заключается в написании нестандартной библиотеки с функциями, работающими независимо от выбранного режима (полноэкранного или оконного). Впрочем, эта книга посвящена программированию для DirectDraw, а не написанию библиотек, поэтому комбинированные приложения в ней не рассматриваются.



Многопоточность


Когда все внимание сосредоточено на курсоре мыши, нетрудно забыть, что курсор— всего лишь часть нашего приложения. После появления курсора приложение не должно принципиально отличаться от рассмотренных выше, так что было бы нежелательно вставлять код ввода от мыши и обновления курсора в середину приложения. И даже если согласиться на это, как будет выглядеть этот код? Он должен постоянно проверять наличие новых данных от мыши. При обнаружении данных он обновляет курсор мыши; в противном случае продолжает свою нормальную работу. Постоянный опрос мыши замедлит приложение и усложнит его структуру. Более удачное решение — разделить приложение на две подзадачи, использовав многопоточность.

Если вы уже знакомы с концепцией многопоточности, этот раздел вам не понадобится. Однако для новичков в нем рассматриваются основные положения, которые необходимо усвоить перед тем, как переходить к программированию. Ни в коем случае не следует рассматривать его как исчерпывающее руководство по многопоточности.



Начальные сведения


Перед тем как переходить к техническим подробностям, давайте кратко рассмотрим важнейшие понятия и концепции работы с видеороликами. Если вам уже приходилось заниматься воспроизведением видео, этот раздел кратко напомнит основные положения.

С концептуальной точки зрения видеофайл представляет собой последовательность растровых изображений, каждое из которых содержит очередной кадр. Обычно все растры последовательности обладают одинаковыми размерами и предназначаются для вывода с постоянной скоростью. Нередко растры сопровождаются звуковыми данными— авторским повествованием, диалогом или музыкой.

Графическая и звуковая (необязательная) часть видеоролика часто называются потоками

(streams). Этот термин говорит о том, что отдельные компоненты ролика (в случае видеоданных — растры) связаны между собой и следуют в определенном порядке. Термин «поток» часто встречается в последующих объяснениях.



Наглядное пояснение


Давайте проиллюстрируем эти концепции несколькими рисунками. Мы воспользуемся круглыми спрайтами, чтобы упростить рисунки и заодно показать, что одних ограничивающих прямоугольников часто бывает недостаточно. Начнем с рис.9.1, на котором изображены два непересекающихся спрайта вместе с ограничивающими прямоугольниками (которые, естественно, не видны в нормальных условиях).

Спрайты на рис. 9.1

не сталкиваются. Код, который нам предстоит написать, должен изучить ситуацию и быстро определить, что столкновения отсутствуют; для этого вполне достаточно ограничивающих прямоугольников. В этом случае нет смысла рассматривать пиксели объектов.

Но что произойдет, если один из спрайтов сдвинется и его ограничивающий прямоугольник пересечется с другим? Теперь определить, произошло столкновение или нет, будет сложнее, потому что придется заниматься проверкой на уровне пикселей. Такая ситуация изображена на рис. 9.2.

Хотя спрайты на рис. 9.2

сталкиваются на уровне ограничивающих прямоугольников, они не сталкиваются на уровне пикселей. Это происходит потому, что перекрывающиеся части прямоугольников не содержат ни одного непрозрачного пикселя (здесь расположены только прозрачные пиксели, лежащие за границей спрайта). В таких ситуациях наша программа должна просматривать пиксели спрайтов.


Рис. 9.1. Два несталкивающихся круглых спрайта

Однако следует заметить, что нам не нужно просматривать все пиксели каждого спрайта. Необходимо проверить лишь пиксели внутри области пересечения.

Область пересечения может содержать непрозрачные пиксели, и это совсем не обязательно означает столкновение. Столкновение происходит лишь в том случае, если есть хотя бы один пиксель экрана, являющийся непрозрачным в обоих спрайтах, как в следующем сценарии. На рис. 9.3 изображены столкнувшиеся спрайты.


Рис. 9.2. Два спрайта, сталкивающиеся на уровне ограничивающих прямоугольников



Рис. 9.3.
Два спрайта, сталкивающиеся на уровне пикселей

Наша программа снова должна проверить пиксели каждого спрайта, но на этот раз определить, что столкновение произошло. Как и в сценарии на рис. 9.2, необходимо рассмотреть лишь пиксели области пересечения. Обратите внимание — после обнаружения столкновения не нужно продолжать изучение пикселей. Проверяющая функция может сэкономить время, сообщая о столкновении сразу же после его обнаружения.

На трех рассмотренных рисунках изображены лишь два спрайта, но вряд ли ваше приложение ограничится таким количеством. Нам потребуется решение, которое можно было бы распространить на произвольное количество спрайтов. Однако вместо того, чтобы изобретать алгоритмы сразу для нескольких спрайтов, можно воспользоваться двухспрайтовым решением и поочередно применить его к каждой паре спрайтов нашего приложения.

Как вы вскоре убедитесь, между спрайтом и поверхностью существуют четкие различия. Спрайт представляет собой уникальный графический объект, входящий в кадр, а поверхность — всего лишь растр, используемый DirectDraw. Следовательно, ничто не мешает вам представить два спрайта одной поверхностью. Более того, это даже полезно для приложений с несколькими похожими объектами. Наш код должен быть написан так, чтобы проверку можно было выполнить для любых двух спрайтов независимо от того, представлены ли они одной поверхностью или разными.


Назначение классов


В реализации программы Bounce используется библиотека MFC, но без традиционной для нее архитектуры «документ/вид». Вместо этого используются классы MFC CWnd и CWinApp. Это позволяет устранить накладные расходы, связанные с архитектурой «документ/вид», и упростить приложение.

Поддержка DirectDraw сосредоточена в классах DirectDrawWin и DirectDrawApp, производных от CWnd и CWinApp соответственно. В свою очередь от DirectDrawWin и DirectDrawApp порождаются еще два класса. Эти классы (в приложении Bounce они называются BounceWin и BounceApp) обеспечивают функциональность, специфическую для конкретного приложения. На рис. 3.8 изображена иерархия этих шести классов вместе с базовыми классами MFC, используемыми в реализации CWnd и CWinApp.


Рис. 3.8. Иерархия классов в программе Bounce

Классы на рисунке соединены стрелками в соответствии с их наследственными связями. Класс CObject, лежащий в основании иерархического дерева, является базовым для всех остальных классов приложения. От него стрелки идут к классам более высокого уровня BounceWin и BounceApp.

Пять классов в нижней части дерева принадлежат MFC. Я кратко опишу эти классы и объясню, для чего они используются в приложении, но не стану приводить код реализации, потому что MFC не относится к теме книги.

Класс CObject является базовым для всех нетривиальных классов MFC. Он обеспечивает самые общие функции классов — операторы присваивания, поддержку сериализации и нестандартные версии операторов new и delete (для отладочных целей). Кроме того, класс CObject содержит виртуальный деструктор. Это гарантирует, что все классы, производные от CObject, будут правильно вести себя во время уничтожения независимо от типа используемого при этом указателя.

Поддержка схем сообщений (message maps) в MFC реализована в классе CCmdTarget, производном от CObject. Схемами сообщений называются макросы, которые ClassWizard включает в классы, чтобы реализовать обработку сообщений. Поскольку классы нашего приложения являются производными от CCmdTarget, для создания схем сообщений можно использовать ClassWizard.



Класс CWinThread используется в MFC для инкапсуляции функций программных потоков (execution thread). CWinThread применяется для написания многопоточных приложений, но в нашем случае он обеспечивает работу одного программного потока, необходимого для работы приложения.

Класс CWinApp, движущая сила всех приложений на базе MFC, использует CWinThread в качестве базового класса и добавляет в него свои функциональные возможности. С помощью поддержки потоков, унаследованной от CWinThread, CWinApp организует получение и доставку сообщений. Именно в нем заключен механизм доставки сообщений, управляющий работой всех Windows-программ. Класс CWinApp является базовым для класса DirectDrawApp, который расширяет поведение CWinApp возможностями, специфическими для DirectDraw. Класс DirectDrawApp управляет поведением приложений DirectDraw, инициализирует класс окна приложения, обновляет содержимое экрана и убирает «мусор» при завершении работы.

Класс CWnd представляет окна в MFC. CWnd - большой класс; он содержит сотни функций и может выполнять практически любую задачу, связанную с окнами. Мы используем класс CWnd в качестве базового для класса DirectDrawWin, дополняющего функциональные возможности CWnd спецификой DirectDraw. Классы, производные от DirectDrawWin, хорошо подходят для разработки приложений DirectDraw.


Не бойтесь плавающей точки


За последние годы технология изготовления процессоров развилась достаточно, чтобы заметно повлиять на подход к написанию программ. Не так давно операции с плавающей точкой выполнялись значительно медленнее операций с фиксированной точкой, поэтому в высокопроизводительных приложениях приходилось использовать нестандартные решения, основанные на вычислениях с фиксированной точкой. Сегодня полноценные операции с плавающей точкой на стандартном чипе Pentium выполняются так же быстро и даже быстрее, чем операции с фиксированной точкой. Нет смысла соревноваться с оптимизированным микропроцессором, так что положитесь на аппаратные усовершенствования и спокойно используйте вычисления с плавающей точкой в своих программах.



Нехватка видеопамяти


Нехватка видеопамяти становится очевидной после написания первого приложения DirectDraw. На большинстве новых видеокарт установлено 4 Мбайт памяти, но многие карты имеют лишь 2 Мбайт. Это прискорбно, потому что при использовании режимов High и True Color даже 4 Мбайт оказывается не так уж много.

С увеличением разрешения проблема становится еще острее. Например, режим 800x600x24 использует почти 2 Мбайт видеопамяти даже без вторичного буфера. В зависимости от объема установленной памяти можно рекомендовать использование различных видеорежимов, обеспечивающих хорошее быстродействие.

Использование видеопамяти следует тщательно продумать. Например, для видеокарт с памятью в 2 и 4 Мбайт можно использовать различные схемы распределения поверхностей. Если ваше приложение работает со множеством мелких поверхностей, попробуйте протестировать его и выяснить, какие поверхности используются чаще других. Часто используемые поверхности следует по возможности размещать в видеопамяти.

Когда свободная видеопамять кончается, поверхности создаются в системной памяти. Хотя системная память и обладает некоторыми преимуществами по сравнению с видеопамятью, быстродействие не относится к их числу. Некоторые видеокарты поддерживают передачу данных из системной памяти через DMA (Direct Memory Access, прямой доступ к памяти), но это временная мера, потому что в ближайшем будущем будет реализована спецификация шины AGP (Accelerated Graphics Port, ускоренный графический порт). AGP позволит видеокарте обращаться к системной памяти с минимальными потерями или без потерь производительности. Более того, AGP-видеокарта может вообще обходиться без видеопамяти и работать исключительно с системной памятью.

Спецификация AGP проектировалась в первую очередь для 3D-приложений, но поскольку библиотека Direct3D построена на базе DirectDraw (и использует DirectDraw для внутреннего распределения памяти), от новых возможностей AGP выигрывает и DirectDraw. К сожалению, для выхода новых аппаратных спецификаций на массовый рынок требуется немало времени. К тому же нет никаких гарантий, что AGP победит — какой-нибудь конструктивный просчет или отсутствие поддержки со стороны производителей может подорвать ее успех.



Объекты и интерфейсы


В COM четко разграничены понятия объектов и интерфейсов. COM-объекты обеспечивают настоящую функциональность, тогда как COM-интерфейсы предоставляют способы для работы с ней. Обращения к COM-объектам никогда не осуществляются напрямую, а только через интерфейсы. Это правило соблюдается так строго, что мы даже не знаем имен COM-объектов. Известны лишь имена интерфейсов, используемых для работы с объектами. Поскольку прямое обращение к COM-объектам невозможно, в дальнейшем речь пойдет в основном об интерфейсах.

COM-объект может поддерживать сразу несколько интерфейсов. На первый взгляд это может показаться странным, но все объясняется тем, что в соответствии со спецификацией COM-интерфейс после своего определения не может быть изменен или дополнен. Это было сделано для того, чтобы не нарушать работу старых программ при обновлении COM-объекта. Исходный интерфейс остается неизменным, а для работы с новыми функциональными возможностями объекта добавляется новый альтернативный интерфейс.

Все COM-интерфейсы являются производными от интерфейса IUnknown. Префикс I (от слова interface, то есть интерфейс) является стандартным для имен COM-интерфейсов. Имена всех интерфейсов DirectDraw начинаются с I, однако в документации обычно приводятся без префикса. В этой книге при упоминании COM-интерфейсов префикс I также будет опускаться.

Интерфейс IUnknown содержит три функции, наследуемые всеми COM-интерфейсами.

AddRef()

Release()

QueryInterface

Функции AddRef() и Release() обеспечивают поддержку такого средства COM, как инкапсуляция времени существования (lifetime encapsulation). Она представляет собой протокол, согласно которому каждый объект сам отвечает за свое уничтожение.

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

Функция Release() уменьшает значение внутреннего счетчика ссылок. Ее следует применять при завершении работы с указателем или его выходе из области видимости. Обе функции, AddRef() и Release(), возвращают значение, равное новому состоянию счетчика ссылок объекта.

Функция QueryInterface() позволяет обратиться к COM-объекту с запросом о том, поддерживает ли он тот или иной интерфейс. Вспомните, например, что обновленные COM-объекты предоставляют дополнительные интерфейсы, не изменяя существующих. Если данный интерфейс не поддерживается запрашиваемым объектом, возвращается указатель на альтернативный интерфейс.



Обнаружение видеорежимов


На следующем этапе необходимо определить все видеорежимы, поддерживаемые инициализированным драйвером DirectDraw. Для перечисления видеорежимов используется функция EnumDisplayModes(), аналогичная рассмотренной выше функции DirectDrawEnumerate(). В обоих случаях для перечисления используются косвенно вызываемые функции, а также предоставляются средства для передачи им данных, определяемых приложением. В нашем случае DisplayModeAvailable() является функцией косвенного вызова (callback function), а указатель this ссылается на произвольные данные. Функция DisplayModeAvailable() выглядит так:


HRESULT WINAPI DirectDrawWin::DisplayModeAvailable( LPDDSURFACEDESC desc, LPVOID p ) { DirectDrawWin* win=(DirectDrawWin*)p; int& count=win->totaldisplaymodes; if (count==MAXDISPLAYMODES) return DDENUMRET_CANCEL;

win->displaymode[count].width = desc->dwWidth; win->displaymode[count].height = desc->dwHeight; win->displaymode[count].depth = desc->ddpfPixelFormat.dwRGBBitCount;

count++;

return DDENUMRET_OK; }


DirectDraw вызывает функцию DisplayModeAvailable() для каждого поддерживаемого видеорежима. Структура DDSURFACEDESC, передаваемая косвенно вызываемой функции, содержит описание обнаруженного видеорежима. Функция DisplayModeAvailable() сохраняет разрешение экрана и глубину пикселей в специальном массиве, называемом displaymode. В переменной total displaymodes хранится количество обнаруженных видеорежимов; если значение totaldisplaymodes достигает MAXDISPLAYMODES, перечисление завершается возвратом кода DDENUMRET_CANCEL.

Затем функция OnCreate() сортирует элементы displaymode так, чтобы режимы с низким разрешением находились в начале массива. Это делается с помощью функции Win32 qsort(), которой передается функция косвенного вызова для «сравнения» видеорежимов. В нашем случае используется функция CompareModes(), которая сравнивает видеорежимы сначала по разрешению, а затем по глубине пикселей. Я пропускаю дальнейшее обсуждение CompareModes(), потому что оно не имеет никакого отношения к DirectDraw.



Обнаружение видеорежимов и частот смены кадров


В главе 3 говорилось о том, как функция EnumDisplayModes() интерфейса DirectDraw перечисляет все поддерживаемые видеорежимы. Через косвенно вызываемую функцию она сообщает вашему приложению о каждом видеорежиме, поддерживаемом установленными видеоустройствами. Прототип функции EnumDisplayModes() выглядит так:


HRESULT EnumDisplayModes( DWORD flags, LPDDSURFACEDESC desc, LPVOID callbackdata, LPDDENUMMODESCALLBACK callback );


Первый аргумент EnumDisplayModes() представляет собой набор флагов для описания дополнительных возможностей. Второй — является указателем на структуру DDSURFACEDESC с описанием необходимых атрибутов видеорежимов. Третий аргумент может использоваться для передачи данных косвенно вызываемой функции при каждом обращении к ней, а четвертый — является указателем на эту функцию. В главе 3 вызов EnumDisplayModes() выглядел так:


ddraw->EnumDisplayModes (0, 0, this, DisplayModeAvailable );


Первый и второй аргументы равны нулю; это означает, что мы не указываем флаги и критерии видеорежимов. Поскольку флаги (первый аргумент) не указаны, каждый обнаруженный видеорежим включается в список один и только один раз. Если бы в первом аргументе был указан флаг DDEDM_REFRESHRATES, каждый видеорежим вошел бы в список столько раз, сколько различных частот смены кадров в нем поддерживается. Такая возможность рассматривается ниже в этой главе при работе с частотами смены кадров в программе SuperSwitch.



Обновление курсора


Итак, курсор мыши должен обновляться прямо на первичной поверхности. При очередном перемещении курсора необходимо выполнить два действия:

Стереть курсор в старом месте.

Нарисовать курсор в новом месте.

Первую задачу можно решить, восстанавливая ранее сохраненную часть первичной поверхности. Затем мы рисуем курсор мыши на первичной поверхности в новом месте. Тем не менее для восстановления изображения придется добавить дополнительный шаг— перед выводом курсора сохранить часть первичной поверхности, которую он займет. В результате получается следующий алгоритм:

Восстановить фоновое изображение в старом месте.

Сохранить часть изображения в новом месте.

Нарисовать курсор в новом месте.

Эти три шага позволяют переместить курсор без переключения страниц, сохранив при этом содержимое первичной поверхности.

И все же такой подход связан с некоторыми ограничениями. Он хорошо работает, если старая область курсора не накладывается на новую. Но если области перекрываются, курсор мерцает, потому что стирание происходит поблизости от места рисования. Чтобы полностью избавиться от мерцания, мы должны одновременно обновлять старую и новую области расположения курсора, а описанный выше алгоритм можно использовать для неперекрывающихся областей курсора.

Прежде чем продолжать, я хотел бы заметить, что чаще встречаются именно перекрывающиеся области. Старая и новая области курсора перекрываются при любом медленном перемещении мыши, а мышь обычно перемещается быстро лишь из одного края экрана в другой. Во всех остальных случаях при выборе конкретного участка экрана курсор перемещается медленно. Следовательно, борьба с мерцанием становится очень важной задачей.

Чтобы справиться с мерцанием, можно обновлять изображение на внеэкранной поверхности. Мы копируем в нее обе области курсора (старую и новую), обновляем изображение, а затем копируем обе области обратно на первичную поверхность как единое целое. Алгоритм состоит из пяти этапов:

Скопировать объединение старой и новой областей курсора на вспомогательную поверхность.



Стереть старый курсор на вспомогательной поверхности.

Сохранить фоновое изображение, занятое новой областью курсора.

Нарисовать новый курсор на вспомогательной поверхности.

Скопировать содержимое вспомогательной поверхности на первичную поверхность.


Используя оба алгоритма (из трех и пяти этапов), мы всегда сможем обновить курсор без мерцания и разрушения основного изображения.

Для реализации двух алгоритмов потребуются три внеэкранные поверхности: поверхность с курсором, поверхность для хранения фонового изображения и вспомогательный буфер для перекрывающихся курсорных областей. Размеры первой и второй поверхностей совпадают с размерами курсора. Однако вспомогательный буфер должен быть вдвое выше и вдвое шире поверхности курсора, чтобы в нем могли разместиться области при минимальном перекрытии (на самом деле при таком размере буфер получается на один пиксель выше и шире, чем необходимо, но это непринципиально).


Обработка пользовательского ввода


При запуске программы Switch текст в нижней части меню подсказывает, какие клавиши управляют работой приложения. Клавиши со стрелками изменяют текущий выделенный видеорежим, клавиша Enter активизирует его (если он не является текущим), а клавиша Escape завершает работу программы. Все эти клавиши обрабатываются функцией OnKeyDown(), создаваемой ClassWizard. Ее код приведен в листинге 4.5.

Листинг 4.5. Функция SwitchWin::OnKeyDown()


void SwitchWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { int newindex; int nmodes=GetNumDisplayModes(); if (nmodes>maxmodes) nmodes=maxmodes; int rows=nmodes/menucols; if (nmodes%menucols) rows++;

switch (nChar) { case VK_ESCAPE: PostMessage( WM_CLOSE ); break; case VK_UP: newindex=selectmode-1; if (newindex>=0) { selectmode=newindex; UpdateMenuSurface(); } break; case VK_DOWN: newindex=selectmode+1; if (newindex<nmodes) { selectmode=newindex; UpdateMenuSurface(); } break; case VK_LEFT: newindex=selectmode-rows; if (newindex>=0) { selectmode=newindex; UpdateMenuSurface(); } break; case VK_RIGHT:

newindex=selectmode+rows; if (newindex<nmodes) { selectmode=newindex; UpdateMenuSurface(); } break; case VK_RETURN: if (selectmode != GetCurDisplayMode()) { ActivateDisplayMode( selectmode ); x=y=0; } break; case 'S': SaveSurface( primsurf, "switch.bmp" ); break; case 'M': SaveSurface( menusurf, "menusurf.bmp" ); break; case 'F': SaveSurface( fpssurf, "fpssurf.bmp" ); break; case 'T': SaveSurface( bmpsurf, "trisurf.bmp" ); break; default: DirectDrawWin::OnKeyDown(nChar, nRepCnt, nFlags); } }


Обработка нажатых клавиш происходит в различных секциях оператора switch. Клавиша Escape (код виртуальной клавиши VK_ESCAPE) приводит к посылке сообщения WM_CLOSE и последующему завершению приложения. При нажатии клавиш со стрелками изменяется индекс текущего видеорежима и вызывается функция UpdateMenuSurface(), которая перерисовывает menusurf в соответствии с произведенными изменениями. При нажатии клавиши Enter (VK_RETURN) вызывается функция ActivateDisplayMode(), которой в качестве аргумента передается индекс режима (при условии, что выбран видеорежим, отличный от текущего). Все остальные клавиши, нажатые пользователем, обрабатываются функцией OnKeyDown() базового класса.


Теперь в программу необходимо включить код для обработки пользовательского ввода при работе с меню частот. Мы воспользуемся функцией OnKeyDown() (листинг 4.7).

Листинг 4.7. Функция SuperSwitch::OnKeyDown()


void SuperSwitchWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { int newindex; int nmodes=GetNumDisplayModes(); if (nmodes>maxmodes) nmodes=maxmodes; int rows=nmodes/menucols; if (nmodes%menucols) rows++;

switch (nChar)

{ case VK_ESCAPE: if (!include_refresh || !ratemenu_up) { PostMessage( WM_CLOSE ); break; } if (ratemenu_up) { ratemenu_up=FALSE; if (ratemenusurf) ratemenusurf->Release(), ratemenusurf=0; } break; case VK_UP: if ( include_refresh && ratemenu_up) { if (selectrate>0) { selectrate--; UpdateRateMenuSurface(); } } else { newindex=selectmode-1; if (newindex>=0) { selectmode=newindex; UpdateModeMenuSurface(); } } break; case VK_DOWN: if (include_refresh && ratemenu_up) { if (selectrate<numrates-1) { selectrate++; UpdateRateMenuSurface(); } } else { newindex=selectmode+1; if (newindex<nmodes) { selectmode=newindex; UpdateModeMenuSurface(); } } break; case VK_LEFT: if (include_refresh && ratemenu_up) break; newindex=selectmode-rows; if (newindex>=0) { selectmode=newindex; UpdateModeMenuSurface(); } break; case VK_RIGHT: if (include_refresh && ratemenu_up) break; newindex=selectmode+rows; if (newindex<nmodes) { selectmode=newindex; UpdateModeMenuSurface(); } break; case VK_RETURN: if (include_refresh) { if (ratemenu_up) { int rate=refresh_rates[selectmode][selectrate]; ActivateDisplayMode( selectmode, rate ); x=y=0; ratemenu_up=FALSE; } else { CreateRateMenuSurface(); UpdateRateMenuSurface(); ratemenu_up=TRUE; } } else { if (selectmode!=GetCurDisplayMode()) { ActivateDisplayMode( selectmode ); x=y=0; } } break; case 'S': SaveSurface( primsurf, "SuperSwitch.bmp" ); break; default: DirectDrawWin::OnKeyDown(nChar, nRepCnt, nFlags); }

}


Все case-секции оператора switch были изменены для работы с новым меню.


Давайте посмотрим, как в нашей программе организована обработка ввода. Нажатые клавиши обрабатываются функций OnKeyDown(), которая выглядит так:


void BmpViewWin::OnKeyDown(UINT key, UINT nRepCnt, UINT nFlags) { switch (key) { case VK_UP: Up(); break; case VK_DOWN: Down(); break; case VK_LEFT: Left(); break; case VK_RIGHT: Right(); break; case VK_HOME: Home(); break; case VK_END: End(); break; case VK_PRIOR: PageUp(); break; case VK_NEXT: PageDown(); break; case VK_ESCAPE: case VK_SPACE: case VK_RETURN: ShowDialog(); break; }

DirectDrawWin::OnKeyDown(key, nRepCnt, nFlags); }


С первого взгляда на листинг OnKeyDown() можно разве что понять, какие клавиши обрабатываются программой, потому что вся содержательная работа поручается другим функциям. Обратите внимание — при нажатии клавиш Escape, пробел и Enter вызывается одна и та же функция ShowDialog(). Это облегчает вызов диалогового окна после вывода изображения.

Остальные восемь функций, вызываемых функцией OnKeyDown(), изменяют положение поверхности во время прокрутки:

Up()

Down()

Left()

Right()

Home()

End()

PageUp()

PageDown()

Каждая из этих функций определяет положение поверхности по значениям переменных x, y, xlimit, ylimit, xscroll и yscroll. Код всех восьми функций приведен в листинге 5.9.

Листинг 5.9. Функции смещения поверхности


void BmpViewWin::Up(int inc) { if (!yscroll) return;

if (y+inc<0) { y+=inc; update_screen=TRUE; } else { y=0; update_screen=TRUE; } }

void BmpViewWin::Down(int inc) { if (!yscroll) return;

if (y-inc>=ylimit) { y-=inc; update_screen=TRUE; } else { y=ylimit; update_screen=TRUE; } }

void BmpViewWin::Left(int inc) { if (!xscroll) return;

if (x+inc<0) { x+=inc; update_screen=TRUE; } else { x=0; update_screen=TRUE; } }

void BmpViewWin::Right(int inc) { if (!xscroll) return;

if (x-inc>=xlimit) { x-=inc; update_screen=TRUE; } else { x=xlimit; update_screen=TRUE; } }

void BmpViewWin::Home() { if (xscroll && x!=0) { x=0; update_screen=TRUE; } if (yscroll && y!=0) { y=0; update_screen=TRUE; } }

void BmpViewWin::End() { if (yscroll) { y=-(bmprect.Height()-displayrect.Height()); update_screen=TRUE; } if (xscroll) { x=-(bmprect.Width()-displayrect.Width()); update_screen=TRUE; } }

void BmpViewWin::PageUp() { if (yscroll) { if (y-displayrect.Height()>0) { y-=displayrect.Height(); update_screen=TRUE; } else { y=0; update_screen=TRUE; } } }

void BmpViewWin::PageDown() { if (yscroll) { if (y+displayrect.Height()<=ylimit) { y+=displayrect.Height(); update_screen=TRUE; } else { y=ylimit; update_screen=TRUE; } } }


Обработчикам клавиш со стрелками (Up(), Down(), Left(), Right()) можно передать необязательный аргумент, который определяет шаг прокрутки. Как видно из определения класса BmpViewWin (см.
листинг 5.5), по умолчанию шаг прокрутки равен 4.




В программе AviPlay ввод не играет особой роли. Программа реагирует всего на три клавиши, причем одинаково. Ввод с клавиатуры обрабатывается функцией OnKeyDown():


void AviPlayWin::OnKeyDown(UINT key, UINT nRepCnt, UINT nFlags) { switch (key) { case VK_ESCAPE: case VK_SPACE: case VK_RETURN: ShowDialog(); break; }

DirectDrawWin::OnKeyDown(key, nRepCnt, nFlags); } Все три клавиши вызывают функцию ShowDialog(). Аналогично обрабатывается и ввод от мыши, это происходит в функции OnRButtonDown(): void AviPlayWin::OnRButtonDown(UINT nFlags, CPoint point) { ShowDialog(); DirectDrawWin::OnRButtonDown(nFlags, point); }


Все три клавиши вызывают функцию ShowDialog(). Аналогично обрабатывается и ввод от мыши, это происходит в функции OnRButtonDown():


void AviPlayWin::OnRButtonDown(UINT nFlags, CPoint point) { ShowDialog(); DirectDrawWin::OnRButtonDown(nFlags, point); }


Когда пользователь закрывает диалоговое окно для выбора AVI-файла, функция ShowDialog() посылает сообщение WM_CLOSE, сигнализируя о завершении приложения.



Общее решение


За прошедшие годы было написано огромное количество приложений, в которых выполнялась проверка столкновений. Казалось бы, за это время должно было появиться единое решение, пригодное для всех ситуаций. Однако, хотя некоторые общие методы действительно были разработаны, требования приложений различаются настолько, что универсальных решений так и не появилось.

Одно из таких существенно различающихся требований — точность. Некоторые приложения, особенно быстрые аркадные игры с прокруткой экрана, не нуждаются (да и не могут себе позволить) проверку столкновений на уровне пикселей. Для многих приложений хватает проверки на уровне ограничивающего прямоугольника или сферы. Если приложение требует большей точности и ограничивающие прямоугольники и сферы не подходят, а проверка на уровне пикселей обходится слишком дорого, приходится изобретать нестандартные решения.

Однако в некоторых приложениях необходима именно точность на уровне пикселей. В частности, высокая точность необходима для медленных, плавно движущихся спрайтов, потому что пользователь видит, когда именно должно произойти столкновение.

Итак, не существует единого решения, которые бы обеспечило проверку столкновений для всех приложений с идеальной точностью и приемлемым снижением быстродействия. Однако в тех случаях, когда необходима точность на уровне пикселей, желательно комбинировать точную проверку с ограничивающими прямоугольниками. При таком сочетании ограничивающие прямоугольники применяются для ускоренной проверки, а проверка на уровне пикселей остается лишь для тех случаев, когда это действительно необходимо. Именно такой вариант будет использован в этой главе.



Оконные приложения


Все приложения DirectDraw делятся на два основных типа: оконные (windowed) и полноэкранные (full-screen). Оконное приложение DirectDraw выглядит, как обычная Windows-программа. О полноэкранных приложениях речь пойдет ниже.

В оконных приложениях присутствуют интерфейсные элементы, знакомые нам по традиционным Windows-приложениям, — рамка, строка заголовка и меню. Поскольку оконные приложения DirectDraw отображаются на рабочем столе вместе с окнами остальных приложений, они вынуждены использовать видеорежим (разрешение экрана и глубину пикселей), установленный в Windows в настоящий момент.

Оконные приложения содержат первичную поверхность, однако не могут выполнять «настоящее» переключение страниц. Кроме того, первичная поверхность не соответствует клиентской области окна (то есть области, расположенной внутри рамки). Вместо этого первичная поверхность изображает весь рабочий стол. Это означает, что ваша программа должна следить за расположением окна и его размерами, чтобы графика выводилась в нужном месте. Другими словами, оконное приложение DirectDraw может рисовать на всем рабочем столе.

Так как переключение страниц запрещено, изображения приходится копировать на первичную поверхность из внеэкранных буферов. Это увеличивает вероятность расхождения, потому что блиттинг происходит медленнее, чем переключение страниц. Чтобы избежать расхождения, приходится синхронизировать блиттинг со сменой кадров на мониторе.

Оконные приложения хорошо работают в том случае, если в видеопамяти удается разместить внеэкранный буфер для клиентской области окна. В этом случае содержимое окна можно построить на внеэкранной поверхности, а затем быстро скопировать на первичную поверхность с применением аппаратного ускорения.

Когда нехватка памяти заставляет вас создавать внеэкранные буфера в системной памяти, быстродействие заметно падает. К сожалению, эта ситуация встречается очень часто, особенно для видеокарт с памятью объемом 2 Мбайт, потому что для рабочего стола Windows большинство пользователей выбирает режимы с высоким разрешением.
Например, в режиме 1024x768x16 только для первичной поверхности требуется почти 2 Мбайт памяти. При наличии только 2 Мбайт видеопамяти для внеэкранных поверхностей остается слишком мало места.

Другая проблема, характерная для оконных приложений, - отсечение. Хорошо написанное приложение должно иметь объект отсечения, присоединенный к первичной поверхности. При этом снижается быстродействие программы, потому что каждую операцию блиттинга приходится выполнять в нескольких мелких прямоугольных областях, в соответствии со списком областей отсечения. Кроме того, не удается использовать оптимизированную функцию BltFast(). Блиттинг приходится выполнять с помощью медленной (и громоздкой) функции Blt().

Наконец, в оконных приложениях не удается полностью управлять содержимым палитры. Так как Windows резервирует 20 элементов палитры, можно задать лишь 236 из 256 возможных цветов. Зарезервированные цвета занимают первые и последние 10 элементов системной палитры. Следовательно, на долю палитровых изображений остаются лишь 236 «средних» цветов.


Определение класса


В программе BmpView, как и в других программах этой книги, класс окна приложения является производным от класса DirectDrawWin. К сожалению, по нашему соглашению об именах имя производного класса образуется из имени приложения и суффикса Win. Следовательно, класс окна приложения BmpView называется BmpViewWin, что выглядит несколько неуклюже. Объявление класса BmpViewWin приведено в листинге 5.5.

Листинг 5.5. Класс BmpViewWin


class BmpViewWin : public DirectDrawWin { public: BmpViewWin(); protected: //{{AFX_MSG(BmpViewWin) afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnRButtonDown(UINT nFlags, CPoint point); afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces() { return TRUE; } void DrawScene(); void RestoreSurfaces(); void GetSystemPalette(); void ShowDialog(); BOOL LoadBmp(); void PageUp(); void PageDown(); void Home(); void End(); void Left(int inc=4); void Right(int inc=4); void Up(int inc=4); void Down(int inc=4); private: BmpDialog* bmpdialog; LPDIRECTDRAWPALETTE syspal;

CString fullfilename; CString filename; CString pathname;

CRect displayrect; LPDIRECTDRAWSURFACE bmpsurf; CRect bmprect; int x,y; int xscroll, yscroll; int xlimit, ylimit; BOOL update_screen; DisplayModeArray palettemode, nonpalettemode; };


Единственной открытой (public) функцией класса является конструктор, используемый для инициализации переменных. Далее мы объявляем четыре обработчика сообщений:

OnKeyDown()

OnRButtonDown()

OnCreate()

OnDestroy()

Функция OnKeyDonw() обрабатывает нажатия нескольких клавиш, среди которых клавиши со стрелками, Home, End, Page Up, Page Down, Enter, пробел и Escape.

Функции OnCreate() и OnDestroy() предназначены соответственно для инициализации и освобождения структур данных приложения. В частности, функция OnCreate() создает диалоговое окно для выбора BMP-файла, а функция OnDestroy() уничтожает его.



Далее следуют объявления нескольких закрытых переменных. Функция SelectInitialDisplayMode() похожа на версию, созданную DirectDraw AppWizard и использованную в прошлых программах, но в нее внесены некоторые изменения. Кроме выбора исходного видеорежима, эта функция сохраняет текущую палитру Windows с помощью функции GetSystemPalette() (которая объявляется несколькими строками ниже функции SelectInitialDisplayMode()).

Функция CreateCustomSurfaces() объявляется и определяется в объявлении класса. В отличие от других программ, рассмотренных нами, BmpView не отображает никаких вспомогательных поверхностей, поэтому эта функция не делает ничего. Однако из-за того, что функция DirectDrawWin::CreateCustomSurfaces() является чисто виртуальной, нам приходится объявить и определить ее минимальную реализацию.

Функция DrawScene() отвечает за графический вывод и переключение страниц. Поскольку нашей программе незачем постоянно обновлять экран, функция DrawScene() делает это лишь в ответ на пользовательский ввод. Этим она отличается от других программ, в которых экран обновлялся непрерывно. Функция RestoreSurfaces() восстанавливает поверхности в случае их потери.

Функция ShowDialog() выводит диалоговое окно для выбора BMP-файла. Функция LoadBmp() по имени, полученному из диалогового окна, загружает BMP-файл на поверхность и инициализирует переменные x, y, xscroll, yscroll, xlimit и ylimit. Эти переменные предназначены для позиционирования поверхности в случае, если размер поверхности BMP-файла превышает размеры первичной поверхности.

Затем мы объявляем восемь функций, вызываемых при нажатии конкретных клавиш:

PageUp()

PageDown()

Home()

End()

Left()

Right()

Up()

Down()


Класс содержит несколько переменных, часть из которых упоминалась выше. Их назначение рассматривается при описании функций.




Определение состояния клавиш


Теперь по указателю на интерфейс клавиатуры можно определить состояние отдельных клавиш. В нашей программе это происходит в функции DrawScene(), перед обновлением экрана. Функция DrawScene() приведена в листинге 6.3.

Листинг 6.3. Функция QwertyWin::DrawScene()


void QwertyWin::DrawScene() { static char key[256]; keyboard->GetDeviceState( sizeof(key), &key );

//---------- Клавиши QWERTY -------- if ( key[DIK_Q] & 0x80 ) BltSurface( backsurf, q_dn, 213, 70 ); else BltSurface( backsurf, q_up, 213, 70 );

if ( key[DIK_W] & 0x80 ) BltSurface( backsurf, w_dn, 251, 70 ); else BltSurface( backsurf, w_up, 251, 70 );

if ( key[DIK_E] & 0x80 ) BltSurface( backsurf, e_dn, 298, 70 ); else BltSurface( backsurf, e_up, 298, 70 );

if ( key[DIK_R] & 0x80 ) BltSurface( backsurf, r_dn, 328, 70 ); else BltSurface( backsurf, r_up, 328, 70 );

if ( key[DIK_T] & 0x80 ) BltSurface( backsurf, t_dn, 361, 70 ); else BltSurface( backsurf, t_up, 361, 70 );

if ( key[DIK_Y] & 0x80 ) BltSurface( backsurf, y_dn, 393, 70 ); else BltSurface( backsurf, y_up, 393, 70 ); //---------------- LEFT CONTROL --------------- if ( key[DIK_LCONTROL] & 0x80 ) BltSurface( backsurf, lctrl_dn, 50, 180 ); else BltSurface( backsurf, lctrl_up, 49, 180 );

//---------------- RIGHT CONTROL --------------- if ( key[DIK_RCONTROL] & 0x80 ) BltSurface( backsurf, rctrl_dn, 490, 180 ); else BltSurface( backsurf, rctrl_up, 490, 180 );

//---------------- LEFT ALT --------------- if ( key[DIK_LMENU] & 0x80 ) BltSurface( backsurf, lalt_dn, 100, 260 ); else BltSurface( backsurf, lalt_up, 100, 260 );

//---------------- RIGHT ALT --------------- if ( key[DIK_RMENU] & 0x80 ) BltSurface( backsurf, ralt_dn, 440, 260 ); else BltSurface( backsurf, ralt_up, 440, 260 );

//---------------- SPACE ----------------- if ( key[DIK_SPACE] & 0x80 ) BltSurface( backsurf, space_dn, 170, 340 ); else BltSurface( backsurf, space_up, 170, 340 );

//---------- ESCAPE ------------- if ( key[DIK_ESCAPE] & 0x80 ) { BltSurface( backsurf, esc_dn, 0, 0 ); esc_pressed=TRUE; } else { BltSurface( backsurf, esc_up, 0, 0 ); if (esc_pressed) PostMessage( WM_CLOSE ); }

primsurf->Flip( 0, DDFLIP_WAIT ); }

<
br>Состояние устройства определяется функцией GetDeviceState() интерфейса DirectInputDevice. Тип и размер второго аргумента GetDeviceState() зависят от типа устройства, а также от формата данных, заданного функцией SetDataFormat(). Для клавиатуры функция должна получать массив из 256 байт, где каждый байт соответствует одной клавише. В DirectInput предусмотрен набор клавиатурных констант, которые используются как индексы массива и позволяют ссылаться на нужные клавиши. DirectInput обозначает нажатие клавиши установкой старшего бита того байта, который представляет данную клавишу. Объявление массива и вызов функции GetDeviceState() находятся в верхней части
листинга 6.3, я снова привожу их:

static char key[256]; keyboard->GetDeviceState( sizeof(key), &key );


Адрес массива клавиш передается во втором аргументе GetDeviceState(). Первый аргумент определяет размер данных в байтах.

Все готово к проверке элементов массива. Сначала мы проверяем, была ли нажата клавиша Q:

if ( key[DIK_Q] & 0x80 ) BltSurface( backsurf, q_dn, 213, 70 ); else BltSurface( backsurf, q_up, 213, 70 );


Константа DIK_Q определяет индекс клавиши Q в массиве. Мы проверяем значение старшего бита; если бит установлен, значит, клавиша Q нажата, и мы копируем поверхность, изображающую клавишу Q в нажатом состоянии (q_dn), функцией BltSurface(). Если клавиша не нажата, копируется поверхность q_up.

Обратите внимание: каждой клавише соответствует отдельный элемент массива, даже для функционально одинаковых клавиш. Например, две клавиши Alt обрабатываются по отдельности. Кроме того, DirectInput не отличает прописных букв от строчных. Чтобы учесть регистр буквы, придется дополнительно проверить состояние обеих клавиш Shift.

Оставшаяся часть функции состоит из аналогичных проверок состояния других клавиш. После того как все клавиши будут проверены, функция Flip() интерфейса DirectDrawSurface выводит новое изображение на экран.


Опросы и оповещения


Независимо от конкретной схемы приложение в какой-то момент должно получить данные. Чаще всего это делается путем опроса (polling) устройства через подходящие промежутки времени. Например, приложение может проверять наличие новых данных (непосредственных или буферизованных) при каждом обновлении экрана.

Кроме опроса устройств существует и альтернативный вариант — оповещение (notification). В этом случае программный поток (thread) блокируется и ожидает поступления оповещающего события. С наступлением такого события поток автоматически активизируется. Оповещение позволяет приложению реагировать на изменения в состоянии устройства ввода без расходов процессорного времени, связанных с опросом.

Для однопоточных приложений оповещение используется редко, потому что во время ожидания события поток блокируется и не может ничего делать. Если только вся работа приложения не сводится к получению ввода от пользователя, для оповещения потребуется по крайней мере два потока. В программах этой главы оповещение не используется, однако мы встретимся с ним в главе 7.



Организация доступа к поверхностям


В наших программах чтением BMP-файлов занимается класс DirectDrawWin. Впервые эта возможность была использована в главе 3, где в программе Bounce BMP-файл загружался на поверхность. То же самое происходит и в программе BmpView, но сначала давайте рассмотрим соответствующий программный код.

Поддержка работы с BMP-файлами в классе DirectDrawWin обеспечивается функцией CreateSurface(). Существуют две версии CreateSurface(): первая в качестве аргументов получает параметры поверхности, а вторая - имя BMP-файла. Вторая версия CreateSurface() загружает BMP-файл, затем создает новую поверхность, параметры которой совпадают с параметрами изображения, и копирует содержимое файла на поверхность.



Ошибка переключения режимов DirectDraw


В DirectDraw есть одна ошибка, связанная с переключением режимов, которую никак не может исправить Microsoft (или тот, кто для них пишет видеодрайверы). Впервые я столкнулся с ней во время работы над предыдущей книгой. Я надеялся, что к моменту написания этой книги ошибку уже исправят, но она так и осталась.

Проблемы возникают при активизации видеорежимов, которые отличаются по глубине пикселей от текущего активного режима Windows. Например, если Windows работает в 8-битном видеорежиме, а ваше приложение DirectDraw попытается активизировать 16-битный видеорежим, ничего не получится, даже когда новый режим вполне допустим. Если попытаться установить 8-битный видеорежим при 16-битном режиме Windows, результат будет тем же. В таких случаях DirectX выдает отладочное сообщение следующего вида:


DDHEL: ChangeDisplaySettings LIED!!!

DDHEL: Wanted 640x480x16 got 1024x768x8

DDHEL: ChangeDisplaySettings FAILED: returned -1


К счастью, это происходит только при первой попытке изменения видеорежима. Если эта попытка удалась, после этого можно установить любой видеорежим. Следовательно, у нас появляется обходной путь: если текущий видеорежим отличается по глубине пикселей от желаемого, переключение следует производить в два этапа; сначала перейдите к видеорежиму, который совпадает по глубине пикселей с текущим, а затем— к видеорежиму, нужному вам.

Такой обходной маневр нетрудно реализовать с помощью класса DirectDrawWin, представленного в этой книге. В функции SelectInitialDisplayMode()

(которая вызывается до переключения видеорежима) следует проверить глубину пикселей текущего режима. Если она отличается от требуемой, выполните «фиктивное» переключение. Программа может выглядеть так:


int SampleWin::SelectInitialDisplayMode() { DWORD curdepth=GetDisplayDepth(); int i, nummodes=GetNumDisplayModes(); DWORD w,h,d;

if (curdepth!=16) ddraw2->SetDisplayMode(640, 480, curdepth, 0, 0 ); // Искать режим 640x480x16 после смены видеорежима for (i=0;i>nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==640 && h==480 && d==16) return i; }

return -1; }

<

Основной поток


Основной поток программы Cursor ведет себя почти так же, как и основные потоки всех остальных программ, рассмотренных нами. Он инициализирует DirectDraw, создает поверхности приложения, строит очередной кадр во вторичном буфере и переключает страницы для его отображения. Чтобы обеспечить работу потока ввода, нам придется возложить на основной поток следующие дополнительные задачи:

создание и запуск потока ввода;

обновление курсора перед каждым переключением страниц;

синхронизацию с потоком ввода;

завершение потока ввода.

Основной поток должен в какой-то момент создать и запустить поток ввода (хотя создание и запуск потока можно выполнить одновременно, на самом деле это две разные операции). Поток должен быть запущен в начале работы программы, но следует позаботиться о том, чтобы это не произошло слишком рано. Преждевременный запуск может привести к тому, что поток ввода обнаружит ввод от мыши и попытается обновить курсор до того, как основной поток закончит инициализацию DirectDraw.

Основной поток также должен обновлять курсор перед каждым переключением страниц. После подготовки нового кадра во вторичном буфере, но до переключения страниц, основной поток должен скопировать курсор во вторичный буфер. Однако перед тем, как рисовать курсор, необходимо сохранить соответствующую область вторичного буфера, чтобы поток ввода смог позднее стереть изображение курсора.

Чтобы основной поток не пытался обновить первичную поверхность одновременно с потоком ввода, мы должны синхронизировать работу этих двух потоков. Для основного потока это означает, что операция вывода курсора (во вторичный буфер) и переключение страниц может выполняться лишь после получения доступа к критической секции, используемой для синхронизации потока. Обратите внимание: подготовка вторичного буфера не входит в критическую секцию, потому что основной поток вполне может готовить вторичный буфер, пока поток ввода обновляет содержимое первичной поверхности.

Наконец, основной поток должен предупредить поток ввода о завершении приложения. Помните— поток ввода работает независимо от основного потока. Без извещения со стороны основного потока он не будет знать о том, что приложение собирается завершиться. Кроме того, основной поток не может просто остановить работу потока ввода; поток ввода должен завершиться сам при получении сигнала завершения от основного потока.



Отладка


Говорят, некоторым программистам нравится отлаживать свои программы — я не отношусь к их числу. Приятно узнать, почему ваша программа постоянно «зависает» или почему спрайт неправильно выводится на экран — но я охотно поменял бы это чувство удовлетворения на те напрасно потраченные часы и дни, когда я скрежетал зубами, заново компилировал свои программы и в сотый раз перезагружал компьютер.

Конечно, наша рабочая среда не идеальна, а инструменты еще не прошли всего пути развития. Всю программную отрасль постоянно лихорадит от багов. Они приводят к задержкам, сворачиванию и отмене крупных и мелких проектов. На искоренение особо зловредных багов истрачены многие миллионы долларов.

Но, работая над проектом, содержащим множество багов, вы теряете не только деньги — вы теряете чувство уверенности. Программирование — дело непростое, и не стоит усложнять его попытками внести новые возможности в еще не отлаженную программу. Мы, программисты, видели достаточно багов и привыкли к ним, но такое положение дел никак нельзя считать нормальным.

Иногда ошибки возникают по вине Windows, иногда — по вине DirectX или какой-нибудь библиотеки классов, но подавляющее большинство багов лежит на совести прикладных программистов. Если вы нашли ошибку в программе, лучше бросить все дела и заняться ее искоренением. Не стоит усложнять ситуацию и продолжать работу, зная, что в вашей программе прячутся баги.

Об ошибках и способах отладки написаны многие тома. Особенно ценной я считаю книгу «Writing Solid Code» Стива Магуайра (Steve Maguire) (Microsoft Press, ISBN 1-55615-551-4). Если вы не читали ее ранее, обязательно прочтите. В этом приложении мы ограничимся проблемами и способами отладки, которые относятся к полноэкранным приложениям DirectDraw.



Отладочные макросы


VisualC++ (как и многие другие среды разработки) содержит макросы, предназначенные специально для отладки. Например, макросы TRACE(), ASSERT() и VERIFY()

приносят огромную пользу в процессе разработки. Они предназначены для разных целей, но у всех трех макросов есть нечто общее — они не отягощают итоговый выполняемый файл. При построении отладочных версий вашей программы макросы ведут себя так, как им положено. Однако в «окончательной» версии они удаляются из программы (вместе с теми возможностями, которые ими обеспечивались).

Макрос TRACE()

посылает диагностические сообщения в окно отладчика. Правильно составленные сообщения образуют протокол событий, который можно просмотреть после выполнения программы (а средства удаленной отладки, о которой мы вскоре поговорим, позволяют сделать это даже во время работы программы). Макросы TRACE(), содержащие коды возврата и описания ошибок, помогают отыскать источники багов в ваших программах. Макрос TRACE()

в отличие от двух других нормально работает в полноэкранных приложениях DirectDraw, так что вы можете свободно пользоваться им (этот макрос регулярно встречается в программах на CD-ROM).

Макросы ASSERT() и VERIFY()

очень похожи друг на друга, что часто приводит к недоразумениям. На самом деле между ними существует очень важное различие. В отладочных версиях макросы ASSERT() и VERIFY() работают одинаково. Оба макроса вычисляют выражение, переданное им в качестве аргумента, и прекращают работу программы с выводом сообщения в том случае, если это выражение оказывается равным нулю. Отличие заключается в том, что в окончательной версии макрос ASSERT() вместе с вычисляемым выражением полностью удаляется из кода. С другой стороны, выражение макроса VERIFY() остается в программе. Макрос ASSERT() предназначен для проверки состояния переменных, а VERIFY() — для проверки функций. Выражение макроса ASSERT()

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



Использование макросов ASSERT() и VERIFY()

в полноэкранных приложениях DirectDraw осложняется тем, что при неудачной проверке выводится диалоговое окно. Несовместимость палитры может привести к искажению окна, а из-за переключения страниц окно может и вовсе не появиться на экране.

При таких затруднениях у вас есть два варианта: отказаться от ASSERT() и VERIFY()

или предоставить нестандартные версии, работающие в DirectDraw. Второй вариант предпочтительнее, и, как выясняется, он реализуется достаточно просто.

Если покопаться в заголовочных файлах MFC, вы увидите, что в отладочном режиме макрос ASSERT() определяется так:

#define ASSERT(f) do { if (!(f) && AfxAssertFailedLine(THIS_FILE, __LINE__)) AfxDebugBreak(); } while (0


Выглядит довольно странно. Но вместо того, чтобы пытаться расшифровать логику его работы, мы согласимся с тем, что макрос работает, и попытаемся изменить его так, чтобы он правильно работал в приложениях DirectDraw. Однако перед этим следует заметить, что вывод диалогового окна и завершение приложения выполняются с помощью вызова AfxAssertFailedLine(). Следовательно, любой код, добавленный в этот условный оператор (что на первый взгляд кажется логичным), выполняться не будет.

Теперь давайте подумаем, что нужно сделать для нормального отображения диалогового окна. Можно вызвать функцию DirectDraw FlipToGDISurface()

и обеспечить вывод диалогового окна на первичной поверхности, но проблема с палитрой при этом остается, к тому же окно может быть выведено в неверном видеорежиме. Вместо этого мы воспользуемся функцией RestoreDisplayMode() — это гарантирует вывод диалогового окна, активизацию стандартной палитры Windows и возврат к исходному видеорежиму Windows. Видоизмененный код выглядит так:

#define ASSERT(f) do { if (!(f)) { if (GetDDWin()) { GetDDWin()->GetDDraw()->RestoreDisplayMode(); GetDDWin()->GetDDraw()->Release(); } AfxAssertFailedLine(THIS_FILE, __LINE__); AfxDebugBreak(); } } while (0)


Для работы с объектом DirectDraw применяются функции GetDDWin() и GetDDraw() (которые соответственно возвращают указатели на объект DirectDrawWin и интерфейс DirectDraw).


Помимо вызова функции RestoreDisplayMode() мы для приличия освобождаем объект DirectDraw. Также обратите внимание на перестановку, в результате которой наш код будет выполняться перед вызовом функции AfxAssertFailedLine().

Перейдем к макросу VERIFY(). Возвращаясь к заголовочным файлам MFC, мы находим, что в отладочной версии VERIFY() реализуется с помощью макроса ASSERT():

#define VERIFY(f) ASSERT(f)


Вспомните — в отладочной версии ASSERT() и VERIFY() ведут себя одинаково. Раз ASSERT() и VERIFY() реализуются одним макросом, VERIFY() можно оставить без изменений. Сказанное относится и к окончательным версиям макросов ASSERT() и VERIFY(), потому что нам не потребуется изменять их поведение. При компиляции окончательной версии ASSERT() и VERIFY() определяются так:

#define ASSERT(f) ((void)0) #define VERIFY(f) ((void)(f))


Как было сказано выше, выражение, передаваемое макросу ASSERT(), удаляется из окончательной версии (в действительности оно заменяется выражением ((void)0), которое игнорируется компилятором). Выражения, передаваемые VERIFY(), остаются в коде программы, однако их значение больше не проверяется.

Нам остается лишь переопределить стандартный вариант ASSERT()

из MFC своим нестандартным вариантом. Для этого необходимо сначала отменить определение макроса из MFC. Кроме того, нужно позаботиться о том, чтобы подстановка осуществлялась только в отладочной версии. Окончательный код выглядит так:

#ifdef _DEBUG #undef ASSERT #define ASSERT(f) do { if (!(f)) { if (GetDDWin()) { GetDDWin()->GetDDraw()->RestoreDisplayMode(); GetDDWin()->GetDDraw()->Release(); } AfxAssertFailedLine(THIS_FILE, __LINE__); AfxDebugBreak(); } } while (0) #endif _DEBUG


Модифицированный макрос находится на CD-ROM и готов к работе, поэтому если вы захотите внести изменения в какую-нибудь программу, то можете свободно пользоваться макросами TRACE(), ASSERT() и VERIFY(). Кроме того, этот код автоматически генерируется и включается в проекты, создаваемые DirectDraw AppWizard.


Отладочные сообщения DirectX


Вероятно, вы уже заметили, что при запуске приложений DirectX в окне вывода отладчика VisualC++ появляются диагностические сообщения. По умолчанию отладочные версии компонентов DirectX сообщают таким образом о важных внутренних событиях — например, об ошибках. Типичное окно вывода для приложения DirectDraw изображено на рис. А.10.

СОВЕТ

Прислушивайтесь к крикам DirectX

При установке runtime-части DirectX на рабочий компьютер возникает искушение установить окончательные версии DLL вместо отладочных (во время инсталляции вам предлагается выбрать нужный вариант). Не поддавайтесь соблазну и не устанавливайте окончательные версии! Да, они действительно работают чуть быстрее, и именно с ними будут работать пользователи вашего приложения, но при этом вы лишитесь отладочного вывода. Если во время работы над программой возникнут проблемы, вы так и не узнаете, что же DirectX пытается вам сказать.


Рис. А.10. Типичное окно вывода в отладчике

Возможно, вы не знаете, что детальность этих сообщений тоже можно изменять. В каждой из библиотек DirectDraw, Direct3D и DirectSound можно выбрать пять разных уровней отладочных сообщений. Чтобы настроить уровень отладочных сообщений, выберите значок DirectX в Control Panel и перейдите на вкладку нужного компонента DirectX. Диалоговое окно DirectX Properties с выбранной вкладкой DirectDraw изображено на рис. А.11.


Рис. А.11. Окно DirectX Properties (запускается из Control Panel)

Для библиотеки DirectDraw нажмите кнопку Advanced Settings — откроется окно DirectDraw Advanced Settings. Нужный уровень отладочных сообщений устанавливается с помощью слайдера Debug Level. Окно DirectDraw Advanced Settings изображено на рис. А.12.


Рис. А.12. Окно DirectDraw Advanced Settings

При максимальном уровне отладки каждое приложение DirectDraw, запущенное в отладчике, выдает весьма обширный и подробный протокол. Вывод протокола снижает быстродействие программы, так что максимальный уровень не стоит держать включенным постоянно.
С другой стороны, он сильно помогает в затяжной борьбе с DirectDraw. Чтобы показать, насколько подробная информация выдается при максимальном уровне отладки, я приведу отладочный протокол для небольшого полноэкранного приложения DirectDraw. Обратите внимание на то, что листинг раскрывает некоторые внутренние тонкости работы DirectDraw. Подробный отладочный протокол приведен в листинге А.1.

Листинг А.1. Подробный отладочный протокол DirectDraw

DDraw:====> ENTER: DLLMAIN(baaa12c0): Process Attach: fff00c89,

tid=fff04bf1

DDraw:Thunk connects

DDraw:Signalling DDHELP that a new process has connected

DDraw:====> EXIT: DLLMAIN(baaa12c0): Process Attach: fff00c89

DDraw:createDC(R3D)

DDraw:Enumerating GUID aba52f41-f744-11cf-b4-52-00-00-1d-1b-41-26

DDraw: Driver Name = R3D

DDraw: Description = Righteous 3D DirectX II Driver

DDraw:DeleteDC 0x179e

DDraw:createDC(mm3dfx)

DDraw:Enumerating GUID 3a0cfd01-9320-11cf-ac-a1-00-a0-24-13-c2-e2

DDraw: Driver Name = mm3dfx

DDraw: Description = 3Dfx Interactive DirectX Driver

DDraw:DeleteDC 0x179e

DDraw:Only one Display device in the current system.

DDraw:DirectDrawCreate entered

DDraw: GUID *:00000000, LPLPDD:0064f870, pUnkOuter:00000000

DDraw:Registry already scanned, not doing it again

DDraw:full name = C:\SAMPLE\DEBUG\SAMPLE.EXE

DDraw:name = SAMPLE.EXE

DDraw:DirectDrawCreate: pid = fff00c89

DDraw:Reading Registry

DDraw: ModeXOnly: 0

DDraw: EmulationOnly: 0

DDraw: ShowFrameRate: 0

DDraw: EnablePrintScreen: 0

DDraw: DisableMMX: 0

DDraw: DisableWiderSurfaces:0

DDraw: DisableNoSysLock:0

DDraw: ForceNoSysLock:0

DDraw:Signalling DDHELP to create a new DC

DDraw:createDC(display)

DDraw:DIRECTDRAW driver is wrong version, got 0x5250, expected 0x0100

DDraw:getDisplayMode:

DDraw: bpp=8, refresh=0

DDraw: dwHeight=600, dwWidth=800

DDraw: lStride=0

DDraw:Driver says nummodes=9

DDraw:Enum Display Settings says nummodes=9

DDraw:dwModeIndex = 1

DDraw:Masks for current mode are: 00000000 00000000 00000000

DDraw:DirectDrawObjectCreate: oldpdd == 00000000, reset=0

DDraw:DIRECTDRAW object passed in = 00000000

DDraw:oldpdd == 00000000, reset=0

DDraw:Driver Object: 2256 base bytes

DDraw:dwReserved3 of DDrawGbl is set to 0x0

DDraw:oldpdd == NULL || reset

DDraw:Driver can't blt

DDraw:pddd-&gtlp16DD = 40cf0000

DDraw:Adding ModeX mode 320x200x8 (standard VGA flag is 0)

DDraw:Adding ModeX mode 320x240x8 (standard VGA flag is 0)

DDraw:Adding ModeX mode 320x200x8 (standard VGA flag is 1)

DDraw:All video memory heaps have been disabled. OS has no AGP support

DDraw:Current and Original Mode = 1

DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 1

DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

DDraw:createDC(display)

DDraw:NOT Setting DDCAPS_BANKSWITCHED

DDraw:DeleteDC 0x179e

DDraw:Taking the Win16 lock may not be necessary for VRAM locks

DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0x175e

DDraw:DeleteDC 0x179e

DDraw:Primary's rect is 0, 0, 800, 600

DDraw:HELInit for DISPLAY Driver: Reference Count = 1

DDraw:createDC(display)

DDraw:DeleteDC 0x179e

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0x175e

DDraw:DeleteDC 0x179e

DDraw:***New local allocated 82dc1b1c for global pdrv 82dc11f8

DDraw:New driver object created, interface ptr = 82dc1b84

DDraw: DirectDrawCreate succeeds, and returns ddraw pointer 82dc1b84

DDraw:New driver interface created, 82dc1bb0

DDraw:DD_AddRef, pid=fff00c89, obj=82dc1bb0

DDraw:DD_AddRef, Reference Count: Global = 2 Local = 2 Int = 1

DDraw:DD_Release, pid=fff00c89, obj=82dc1b84

DDraw:DD_Release, Ref Count: Global = 1 Local = 1 Interface = 0

DDraw:*********** ALLOWING MODE X AND VGA MODES

DDraw:DD_GetDeviceRect: display [0 0 800 600]

DDraw:Subclassing window 00000aac

DDraw:StartExclusiveMode

DDraw:******** invalidating all surfaces

DDraw:Enumerating mode 0. 640x480

DDraw:Enumerating mode 1. 800x600

DDraw:Enumerating mode 2. 1024x768

DDraw:Enumerating mode 3. 1280x1024

DDraw:Enumerating mode 4. 640x480

DDraw:Enumerating mode 5. 800x600

DDraw:Enumerating mode 6. 1024x768

DDraw:Enumerating mode 7. 640x480

DDraw:Enumerating mode 8. 800x600

DDraw:Enumerating mode 9. 320x200

DDraw:Enumerating mode 10. 320x240

DDraw:Enumerating mode 11. 320x200

DDraw:Looking for 640x480x8

DDraw:Found 640x480x8x (flags = 1)

DDraw:Found 800x600x8x (flags = 1)

DDraw:Found 1024x768x8x (flags = 1)

DDraw:Found 1280x1024x8x (flags = 1)

DDraw:Found 640x480x16x (flags = 0)

DDraw:Found 800x600x16x (flags = 0)

DDraw:Found 1024x768x16x (flags = 0)

DDraw:Found 640x480x32x (flags = 0)

DDraw:Found 800x600x32x (flags = 0)

DDraw:Found 320x200x8x (flags = 3)

DDraw:Found 320x240x8x (flags = 3)

DDraw:Found 320x200x8x (flags = 11)

DDraw:Calling HEL SetMode

DDraw:width = 640

DDraw:height = 480

DDraw:bpp = 8

DDraw:WM_DISPLAYCHANGE: 640x480x8

DDraw:DD_GetDeviceRect: display [0 0 640 480]

DDraw:WM_SIZE hWnd=AAC wp=0000, lp=01E00280

DDraw:WM_SIZE: Window restored, NOT sending WM_ACTIVATEAPP

DDraw:createDC(display)

DDraw:DeleteDC 0x1712

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0x179e

DDraw:DeleteDC 0x1712

DDraw:createDC(display)

DDraw:getDisplayMode:

DDraw: bpp=8, refresh=0

DDraw: dwHeight=480, dwWidth=640

DDraw: lStride=0

DDraw:Driver says nummodes=9

DDraw:Enum Display Settings says nummodes=9

DDraw:dwModeIndex = 0

DDraw:Masks for current mode are: 00000000 00000000 00000000

DDraw:DirectDrawObjectCreate: oldpdd == 82dc11f8, reset=1

DDraw:DIRECTDRAW object passed in = 82dc11f8

DDraw:oldpdd == 82dc11f8, reset=1

DDraw:Driver Object: 2256 base bytes

DDraw:dwReserved3 of DDrawGbl is set to 0x0

DDraw:oldpdd == NULL || reset

DDraw:Driver can't blt

DDraw:******** invalidating all surfaces

DDraw:All video memory heaps have been disabled. OS has no AGP support

DDraw:Current and Original Mode = 0

DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 0

DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

DDraw:createDC(display)

DDraw:NOT Setting DDCAPS_BANKSWITCHED

DDraw:DeleteDC 0x179e

DDraw:Taking the Win16 lock may not be necessary for VRAM locks

DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0x1776

DDraw:DeleteDC 0x179e

DDraw:Primary's rect is 0, 0, 640, 480

DDraw:DeleteDC 0x1712

DDraw:Looking for 640x480x16

DDraw:Found 640x480x8x (flags = 1)

DDraw:Found 800x600x8x (flags = 1)

DDraw:Found 1024x768x8x (flags = 1)

DDraw:Found 1280x1024x8x (flags = 1)

DDraw:Found 640x480x16x (flags = 0)

DDraw:Found 800x600x16x (flags = 0)

DDraw:Found 1024x768x16x (flags = 0)

DDraw:Found 640x480x32x (flags = 0)

DDraw:Found 800x600x32x (flags = 0)

DDraw:Found 320x200x8x (flags = 3)

DDraw:Found 320x240x8x (flags = 3)

DDraw:Found 320x200x8x (flags = 11)

DDraw:Calling HEL SetMode

DDraw:width = 640

DDraw:height = 480

DDraw:bpp = 16

DDraw:Window 00000ac4 is on top of us!!

DDraw:Window 00000ac4 is on top of us!!

DDraw:Window 00000ac4 is on top of us!!

DDraw:WM_DISPLAYCHANGE: 640x480x16

DDraw:DD_GetDeviceRect: display [0 0 640 480]

DDraw:createDC(display)

DDraw:DeleteDC 0x172a

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0xc96

DDraw:DeleteDC 0x172a

DDraw:createDC(display)

DDraw:getDisplayMode:

DDraw: bpp=16, refresh=0

DDraw: dwHeight=480, dwWidth=640

DDraw: lStride=0

DDraw:Driver says nummodes=9

DDraw:Enum Display Settings says nummodes=9

DDraw:dwModeIndex = 4

DDraw:Masks for current mode are: 00007c00 000003e0 0000001f

DDraw:DirectDrawObjectCreate: oldpdd == 82dc11f8, reset=1

DDraw:DIRECTDRAW object passed in = 82dc11f8

DDraw:oldpdd == 82dc11f8, reset=1

DDraw:Driver Object: 2256 base bytes

DDraw:dwReserved3 of DDrawGbl is set to 0x0

DDraw:oldpdd == NULL || reset

DDraw:Driver can't blt

DDraw:******** invalidating all surfaces

DDraw:All video memory heaps have been disabled. OS has no AGP support

DDraw:Current and Original Mode = 4

DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 4

DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

DDraw:createDC(display)

DDraw:NOT Setting DDCAPS_BANKSWITCHED

DDraw:DeleteDC 0xc96

DDraw:Taking the Win16 lock may not be necessary for VRAM locks

DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0x179e

DDraw:DeleteDC 0xc96

DDraw:Primary's rect is 0, 0, 640, 480

DDraw:DeleteDC 0x172a

DDraw:82dc1bb0-&gtCreateSurface

DDraw: DDSURFACEDESC-&gtdwBackBufferCount = 1

DDraw: DDSURFACEDESC-&gtlpSurface = 00000000

DDraw: DDSCAPS_COMPLEX

DDraw: DDSCAPS_FLIP

DDraw: DDSCAPS_PRIMARYSURFACE

DDraw:******** invalidating all primary surfaces

DDraw:#### Using GDI screen bpp = 16

DDraw:*** allocating primary surface

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0x179e

DDraw:DeleteDC 0xc96

DDraw:#### Using GDI screen bpp = 16

DDraw:*** allocating a backbuffer

DDraw:HEL:About to allocate 614400 bytes for the surface

DDraw:DD_Surface_AddRef, Reference Count: Global = 1 Local = 1 Int = 1

DDraw:DD_Surface_AddRef, Reference Count: Global = 1 Local = 1 Int = 1

DDraw: CreateSurface returns 00000000 (0)

DDraw:DD_Surface_AddRef, Reference Count: Global = 2 Local = 2 Int = 2

DDraw:82dc1bb0-&gtCreateSurface

DDraw: DDSURFACEDESC-&gtdwHeight = 240

DDraw: DDSURFACEDESC-&gtdwWidth = 320

DDraw: DDSURFACEDESC-&gtlpSurface = 00000000

DDraw: DDSCAPS_OFFSCREENPLAIN

DDraw: DDSCAPS_VIDEOMEMORY

DDraw:No hardware support

DDraw: CreateSurface returns 88760233 (563)

DDraw:82dc1bb0-&gtCreateSurface

DDraw: DDSURFACEDESC-&gtdwHeight = 240

DDraw: DDSURFACEDESC-&gtdwWidth = 320

DDraw: DDSURFACEDESC-&gtlpSurface = 00000000

DDraw: DDSCAPS_OFFSCREENPLAIN

DDraw: DDSCAPS_SYSTEMMEMORY

DDraw:Forcing pixel format for explicit system memory surface

DDraw:#### Got surface pixel format bpp = 16

DDraw:*** allocating a surface 320x240x16

DDraw:HEL: About to allocate 153600 bytes for the surface of

640x240 with alignemnt 8

DDraw:DD_Surface_AddRef, Reference Count: Global = 1 Local = 1 Int = 1

DDraw: CreateSurface returns 00000000 (0)

DDraw:82dc1f74-&gtLock

DDraw: DDSURFACEDESC-&gtdwHeight = 240

DDraw: DDSURFACEDESC-&gtdwWidth = 320

DDraw: DDSURFACEDESC-&gtlPitch = 640

DDraw: DDSURFACEDESC-&gtlpSurface = 004b7118

DDraw:Flags:

DDraw: DDPF_RGB

DDraw: BitCount:16

DDraw: Bitmasks: R/Y:00007c00, G/U:000003e0, B/V:0000001f, Alpha/Z:00000000

DDraw: DDSCAPS_OFFSCREENPLAIN

DDraw: DDSCAPS_SYSTEMMEMORY

DDraw:WM_SIZE hWnd=AAC wp=0000, lp=01E00280

DDraw:WM_SIZE: Window restored, sending WM_ACTIVATEAPP

DDraw:WM_ACTIVATEAPP: BEGIN Activating app pid=fff00c89, tid=fff04bf1

DDraw:*** Already activated

DDraw:WM_ACTIVATEAPP: DONE Activating app pid=fff00c89, tid=fff04bf1

DDraw:Bringing window to top

DDraw:WM_ACTIVATEAPP: BEGIN Deactivating app pid=fff00c89, tid=fff04bf1

DDraw:*** Active state changing

DDraw:******** invalidating all surfaces

DDraw:INACTIVE: fff00c89: Restoring original mode (1)

DDraw:In RestoreDisplayMode

DDraw:Turning off DCI in mySetMode

DDraw:WM_DISPLAYCHANGE: 800x600x8

DDraw:DD_GetDeviceRect: display [0 0 800 600]

DDraw:WM_SIZE hWnd=AAC wp=0000, lp=02580320

DDraw:WM_SIZE: Window restored, NOT sending WM_ACTIVATEAPP

DDraw:WM_ACTIVATEAPP: BEGIN Deactivating app pid=fff00c89, tid=fff04bf1

DDraw:*** Already deactivated

DDraw:WM_ACTIVATEAPP: DONE Deactivating app pid=fff00c89, tid=fff04bf1

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0x159a

DDraw:DeleteDC 0xa4a

DDraw:RestoreDisplayMode: Process fff00c89 Mode = 4

DDraw:createDC(display)

DDraw:getDisplayMode:

DDraw: bpp=8, refresh=0

DDraw: dwHeight=600, dwWidth=800

DDraw: lStride=0

DDraw:Driver says nummodes=9

DDraw:Enum Display Settings says nummodes=9

DDraw:dwModeIndex = 1

DDraw:Masks for current mode are: 00000000 00000000 00000000

DDraw:DirectDrawObjectCreate: oldpdd == 82dc11f8, reset=1

DDraw:DIRECTDRAW object passed in = 82dc11f8

DDraw:oldpdd == 82dc11f8, reset=1

DDraw:Driver Object: 2256 base bytes

DDraw:dwReserved3 of DDrawGbl is set to 0x0

DDraw:oldpdd == NULL || reset

DDraw:Driver can't blt

DDraw:******** invalidating all surfaces

DDraw:All video memory heaps have been disabled. OS has no AGP support

DDraw:Current and Original Mode = 1

DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 1

DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

DDraw:createDC(display)

DDraw:NOT Setting DDCAPS_BANKSWITCHED

DDraw:DeleteDC 0x170a

DDraw:Taking the Win16 lock may not be necessary for VRAM locks

DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

DDraw:createDC(display)

DDraw:createDC(display)

DDraw:DeleteDC 0xaa2

DDraw:DeleteDC 0x170a

DDraw:Primary's rect is 0, 0, 800, 600

DDraw:DeleteDC 0x13ba

DDraw:Redrawing all windows

DDraw:DoneExclusiveMode

DDraw:Enabling error mode, hotkeys

DDraw:Mode was never changed by this app

DDraw:WM_ACTIVATEAPP: DONE Deactivating app pid=fff00c89, tid=fff04bf1

DDraw:DD_Surface_Release, Reference Count: Global = 0 Local = 0 Int = 0

DDraw:Deleting attachment from 82dc1e98 to 82dc1b84 (implicit = 1)

DDraw:DeleteOneAttachment: 82dc1b84,82dc1e98

DDraw:DD_Surface_AddRef, Reference Count: Global = 3 Local = 3 Int = 3

DDraw:Leaving AddRef early to prevent recursion

DDraw:DeleteOneLink: 82dc1e98,82dc1b84

DDraw:DeleteOneLink: 82dc1b84,82dc1e98

DDraw:DD_Surface_Release, Reference Count: Global = 2 Local = 2 Int = 2

DDraw:Leaving Release early to prevent recursion

DDraw:DD_Surface_Release, Reference Count: Global = 1 Local = 1 Int = 1

DDraw:DD_Surface_Release, Reference Count: Global = 0 Local = 0 Int = 0

DDraw:Freeing pointer 0042110c

DDraw:DD_Release, pid=fff00c89, obj=82dc1bb0

DDraw:DD_Release, Ref Count: Global = 0 Local = 0 Interface = 0

DDraw:Unsubclassing window 00000aac

DDraw:ProcessSurfaceCleanup

DDraw:Process fff00c89 had 1 accesses to surface 82dc1f74

DDraw:DD_Surface_Release, Reference Count: Global = 0 Local = 0 Int = 0

DDraw:Freeing pointer 004b7118

DDraw:Leaving ProcessSurfaceCleanup

DDraw:ProcessPaletteCleanup, ppal=00000000

DDraw:ProcessClipperCleanup

DDraw:Cleaning up clippers owned by driver object 0x82dc11f8

DDraw:Not cleaning up clippers not owned by a driver object

DDraw:ProcessVideoPortCleanup

DDraw:Leaving ProcessVideoPortCleanup

DDraw:Mode was never changed by this app

DDraw:FREEING DRIVER OBJECT

DDraw:Calling HEL DestroyDriver

DDraw:HEL DestroyDriver: dwHELRefCnt=0

DDraw:3 surfaces allocated - 768000 bytes total

DDraw:*********** DDHEL TIMING INFO ************

DDraw:myFlip: 30 calls, 1.365 sec (0.045)

DDraw:myLock: 1 calls, 0.000 sec (0.000)

DDraw:myUnlock: 1 calls, 0.000 sec (0.000)

DDraw:******************************************

DDraw:Frequency(cycles/second)(0) 1193180

DDraw:Blt16_SrcCopy(32): SUM = 264860

DDraw:Blt16_SrcCopy(32): COUNT = 30

DDraw:Blt16_SrcCopy(32): AVG = 8828

DDraw:Blt16_SrcCopy(32): MIN = 8572

DDraw:Blt16_SrcCopy(32): MAX = 9964

DDraw:Blt16_SrcCopy(32): Dst MB/sec = 17

DDraw:Blt16_ColorFill(47): SUM = 716435

DDraw:Blt16_ColorFill(47): COUNT = 32

DDraw:Blt16_ColorFill(47): AVG = 22388

DDraw:Blt16_ColorFill(47): MIN = 15855

DDraw:Blt16_ColorFill(47): MAX = 55858

DDraw:Blt16_ColorFill(47): Dst MB/sec = 27

DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): SUM = 263495

DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): COUNT = 30

DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): AVG = 8783

DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): MIN = 8528

DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): MAX = 9917

DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): Dst MB/sec = 17

DDraw:Driver is now FREE

DDraw:====> ENTER: DLLMAIN(baaa12c0): Process Detach fff00c89,

tid=fff04bf1

DDraw:MemState

DDraw:Memory still allocated! Alloc count = 11

DDraw:Current Process (pid) = fff00c89

DDraw:82dc100c: dwSize=00000008, lpAddr=baaa1bbc (pid=fff1b349)

DDraw:82dc1054: dwSize=0000001d, lpAddr=baae5093 (pid=fff18e61)

DDraw:82dc1090: dwSize=00000019, lpAddr=baae5093 (pid=fff18e61)

DDraw:82dc10c8: dwSize=00000019, lpAddr=baae5093 (pid=fff18e61)

DDraw:82dc1100: dwSize=0000001d, lpAddr=baae5093 (pid=fff18e61)

DDraw:82dc113c: dwSize=00000018, lpAddr=baae5093 (pid=fff18e61)

DDraw:82dc1170: dwSize=00000019, lpAddr=baae5093 (pid=fff18e61)

DDraw:82dc11a8: dwSize=0000001c, lpAddr=baae52c8 (pid=fff18e61)

DDraw:82dc1030: dwSize=00000008, lpAddr=baaa1bbc (pid=fff04a0d)

DDraw:82dc1fc0: dwSize=00000008, lpAddr=baaa1bbc (pid=fff04a0d)

DDraw:82dc1c3c: dwSize=00000008, lpAddr=baaa1bbc (pid=fff00c89)

DDraw:Total Memory Unfreed From Current Process = 8 bytes

DDraw:====> EXIT: DLLMAIN(baaa12c0): Process Detach fff00c89


Отсечение


В идеальном случае блиттинг сводится к копированию всей поверхности на другую поверхность. Однако на практике источник довольно часто копируется на край приемника, или же приемник частично перекрыт другой поверхностью или окном. В таких случаях применяется отсечение (clipping). Оно позволяет скопировать лишь отдельную часть (или несколько частей) поверхности.

Отсечение чаще всего оказывается необходимым при написании оконных приложений DirectDraw, поскольку эти приложения должны подчиняться «правилам поведения» для рабочего стола Windows. Мы поговорим об оконных приложениях позднее в этой главе.

DirectDraw обеспечивает полноценную поддержку прямоугольного отсечения. Тем не менее в некоторых ситуациях бывает полезно написать свою собственную процедуру отсечения. Нестандартное отсечение рассматривается в главе 3.



Оверлейные функции


Поддержка оверлеев в DirectDrawSurface представлена следующими функциями:

AddOverlayDirtyRect()

EnumOverlayZOrder()

GetOverlayPosition()

SetOverlayPosition()

UpdateOverlay()

UpdateOverlayDisplay()

UpdateOverlayZOrder()

Функции GetOverlayPosition() и SetOverlayPosition() управляют положением оверлеев. Функция UpdateOverlay() изменяет параметры оверлея; в частности, она определяет, должен ли оверлей отображаться на экране и следует ли применять для него альфа-наложение или копирование с цветовым ключом.

Функция UpdateOverlayDisplay() обновляет изображение с учетом новых значений параметров. Данная функция может обновить все изображение оверлея или ограничиться его прямоугольными областями, заданными функцией AddOverlayDirtyRect(). Наконец, функция EnumOverlayZOrders() используется для перебора оверлеев в порядке их Z-координаты (Z-координата определяет, какие оверлеи выводятся поверх других). Возможен перебор как в прямом порядке (от передних оверлеев к задним), так и в обратном (от задних — к передним).



Палитра


Палитра в BMP-файлах хранится в виде списка структур RGBQUAD, где каждый элемент представляет отдельный цвет. Структура RGBQUAD объявляется так:


typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD;


В первых трех полях хранятся цветовые RGB-составляющие. На поле rgbReserved мы не будем обращать внимания (предполагается, что оно равно нулю). Как я упоминал выше, количество структур RGBQUAD в BMP-файле определяется полем biClrUsed. Тем не менее обычно 8-битные BMP-файлы содержат 256 структур RGBQUAD. В 24-битных RGB-файлах структуры RGBQUAD отсутствуют.



Палитры


Приложение, работающее в 8-битном видеорежиме, должно иметь палитру. Палитрой называется таблица цветов, к которой можно обратиться в любой момент. Если бы 8-битные видеорежимы обходились без палитры, то приложениям пришлось бы работать с фиксированным набором из 256 цветов. Палитра же позволяет указать, какие именно 256 цветов будут использоваться в приложении.

При использовании палитровых видеорежимов необходимо позаботиться о том, чтобы во всех графических объектах вашего приложения использовалась одна и та же палитра. В противном случае некоторые объекты будут отображаться в искаженных цветах. Палитры могут причинить немало хлопот, особенно когда вам придется выбирать единую палитру для отображения большого количества графических объектов. Тем не менее они обладают некоторыми преимуществами. Как упоминалось выше, палитры позволяют представить в ограниченном наборе максимальное количество цветов. Кроме того, с помощью палитр можно организовать палитровую анимацию (palette animation).

Палитровой анимацией называется методика, при которой анимация выполняется за счет изменения элементов палитры, а не самих пикселей. Это позволяет мгновенно изменить цвета многих пикселей изображения. В некоторых ситуациях (например, для повторяющихся анимаций) она оказывается полезной.



Передача графических данных


Как видно из функции CreateSurface(), передача графических данных BMP-файла на поверхность осуществляется функцией Copy_Bmp_Surface(). Функция Copy_Bmp_Surface() пользуется услугами четырех вспомогательных функций, каждая из которых специализируется на пикселях определенной глубины. Код Copy_Bmp_Surface() выглядит так:


BOOL DirectDrawWin::Copy_Bmp_Surface( LPDIRECTDRAWSURFACE surf, BITMAPINFOHEADER* bmphdr, BYTE* buf) { if (surf==0 || bmphdr==0 || buf==0) return FALSE;

int imagew=bmphdr->biWidth; int imageh=bmphdr->biHeight; int imagebitdepth=bmphdr->biBitCount;

BOOL ret=FALSE; if (imagebitdepth==8) { if (displaydepth==8) ret=Copy_Bmp08_Surface08( surf, buf, imagew, imageh ); } else if (imagebitdepth==24) { if (displaydepth==16) ret=Copy_Bmp24_Surface16( surf, buf, imagew, imageh ); else if (displaydepth==24) ret=Copy_Bmp24_Surface24( surf, buf, imagew, imageh ); else if (displaydepth==32) ret=Copy_Bmp24_Surface32( surf, buf, imagew, imageh ); }

return ret; }


Вспомогательные функции предназначены для передачи графических данных в зависимости от глубины пикселей BMP-файла и текущего видеорежима. Все четыре функции получают одни и те же четыре аргумента: указатель на поверхность-приемник, буфер с графическими данными из BMP-файла, ширину и высоту изображения. Каждая функция копирует графические данные BMP-файла на поверхность-приемник.



Переключение страниц


До сих пор мы рассматривали обновление курсора мыши без переключения страниц, но ведь приложение должно переключать страницы для обновления экрана. Что же произойдет с нашим тщательно подготовленным курсором после переключения? Он исчезнет. Мы можем нарисовать его заново, но это вызовет мерцание.

Логичнее будет выводить курсор на вторичном буфере после подготовки очередного кадра. Такое решение оказывается удачным, потому что содержимое фонового буфера все равно приходится обновлять перед переключением страниц (в противном случае при следующем обновлении курсора будет восстановлена устаревшая область первичной поверхности). Алгоритм выглядит так:

Построить новый кадр во вторичном буфере.

Сохранить область вторичного буфера, где должен находиться курсор.

Нарисовать курсор на вторичном буфере.

Выполнить переключение страниц.

Теперь курсор можно обновлять при переключении страниц или без него, причем не вызывая мерцания. Однако мы лишь подходим к решению проблемы — нужно придумать, как запрограммировать это решение.



Переключение видеорежимов


Для безопасного вызова функции SetDisplayMode() интерфейса DirectDraw стоит заранее убедиться в том, что нужный вам режим поддерживается. Как мы узнали из главы 3, класс DirectDrawWin с помощью функции EnumDisplayModes() интерфейса DirectDraw строит список всех доступных видеорежимов. Пользуясь функциями доступа, мы можем применить информацию из этого массива для надежного вызова SetDisplayMode().

Тем не менее переключение видеорежимов не сводится к простому вызову функции SetDisplayMode(). По этой причине в класс DirectDrawWin была включена функция ActivateDisplayMode(), которая выполняет все необходимые действия для гладкого перехода от одного режима к другому. Вскоре мы рассмотрим ActivateDisplayMode() и все, что она делает, но сначала давайте обратимся к самой функции SetDisplayMode().



Переменные формата пикселей в классе DirectDrawWin


При описании класса DirectDrawWin в главе 3 мы видели, что функция DirectDrawWin::ActivateDisplayMode() после активизации нового видеорежима, но до создания вспомогательных поверхностей приложения, вызывает функцию StorePixelFormatData(). Описание этой функции было отложено до настоящего момента.

Функция StorePixelFormatData() присваивает значения шести переменным класса DirectDrawWin в соответствии с форматом пикселей текущего активного видеорежима; эти переменные определяют стартовый бит и количество бит для каждой цветовой составляющей пикселя. В следующем разделе мы увидим, как эти переменные используются при манипуляциях с памятью поверхности. Функция StorePixelFormatData() выглядит так:


BOOL DirectDrawWin::StorePixelFormatData() { DDPIXELFORMAT format; ZeroMemory( &format, sizeof(format) ); format.dwSize=sizeof(format); if (primsurf->GetPixelFormat( &format )!=DD_OK) { TRACE("StorePixelFormatData() failed\n"); return FALSE; }

loREDbit = LowBitPos( format.dwRBitMask ); WORD hiREDbit = HighBitPos( format.dwRBitMask ); numREDbits=(WORD)(hiREDbit-loREDbit+1);

loGREENbit = LowBitPos( format.dwGBitMask ); WORD hiGREENbit = HighBitPos( format.dwGBitMask ); numGREENbits=(WORD)(hiGREENbit-loGREENbit+1);

loBLUEbit = LowBitPos( format.dwBBitMask ); WORD hiBLUEbit = HighBitPos( format.dwBBitMask ); numBLUEbits=(WORD)(hiBLUEbit-loBLUEbit+1);

return TRUE; }


Функция StorePixelFormatData() присваивает значения шести переменным формата с помощью масок, полученных функцией GetPixelFormat() интерфейса DirectDrawSurface. Это следующие переменные:

loREDbit

numREDbits

loGREENbit

numGREENbits

loBLUEbit

numBLUEbits

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



Перестановка кресел на «Титанике»


Вспоминая времена своей учебы (особенно в колледже), я понимаю, что в те времена мы слишком беспокоились о быстродействии. Например, один профессор подсчитывал, сколько дополнительных инструкций уходит на вызов функции. Он любил рассуждать о разнице в быстродействии при использовании вызовов функций в стиле C и Паскаля и о преимуществах развертки циклов.

Он вырос в эпоху перфокарт и ассемблера. Его учили тщательно строить программу на ассемблерном уровне, где приходится учитывать каждый байт, поэтому вполне естественно, что он не выносил напрасной траты байтов и команд процессора. Действительно, вызов функции требует нескольких лишних тактов, а уменьшение количества аргументов экономит память в стеке, но что из того? Преимущества, получаемые от вызова функций и от использования аргументов, оправдывают затраты.

По сравнению с техникой, применявшейся в эпоху перфокарт, современный компьютер невероятно быстр и мощен. Экономить каждый байт в современном программном пакете — то же самое, что пытаться переставлять кресла на палубе «Титаника». Никакого толку от этого занятия не будет, хуже того, оно отвлечет вас от решения настоящих проблем с быстродействием.



Поддерживаемые устройства


DirectInput поддерживает практически все устройства ввода, подключаемые к PC. Конечно, речь идет лишь о тех устройствах, для которых существуют драйверы DirectInput. Библиотека обеспечивает настолько исчерпывающую поддержку любых устройств ввода, что она (скорее всего) сможет поддерживать и те устройства, которые еще не изобретены.

Я не пытаюсь обсуждать все устройства, поддерживаемые DirectInput, потому что для этого потребовалась бы отдельная книга. Здесь же DirectInput рассматривается как высокопроизводительная альтернатива традиционному механизму Windows для получения данных.

Возможностей DirectInput хватает даже на обнаружение и поддержку аппаратных конфигураций, при которых к одному компьютеру подключается несколько клавиатур и/или мышей. Впрочем, данная тема тоже выходит за рамки этой главы; мы вполне обойдемся основными мышью и клавиатурой.



Подготовка инструментов


После такой основательной (хотя и несколько поспешной) теоретической лекции можно переходить к практическим вопросам. Перед тем как заняться изучением программ, имеющихся на CD-ROM, или написанием собственных приложений DirectDraw, необходимо подготовить инструментарий. Вам потребуются четыре отдельных программных компонента:

Windows NT или Windows 95

Runtime-файлы DirectX

DirectX SDK

Visual C++ 5.0

Скорее всего, операционная система Windows NT или Windows 95 уже установлена на вашем компьютере. Если вы пользуетесь Windows NT, понадобится версия 4.0 или выше (желательно с установленным Service Pack 3); для Windows 95 подойдет любая версия. Три оставшиеся строки из списка мы рассмотрим немного подробнее.



Подготовка поверхностей


Последняя функция, вызываемая в ActivateDisplayMode() (см. листинг 3.2), — CreateCustomSurfaces(). Эта функция является чисто виртуальной, поэтому классы, производные от DirectDrawWin, должны реализовать ее. Функция CreateCustomSurfaces() создает вспомогательные поверхности, а также инициализирует переменные и структуры данных. В классе BounceWin эта функция выглядит так:


BOOL BounceWin::CreateCustomSurfaces() { CString filename; if (GetCurDisplayDepth()==8) filename="tri08.bmp"; else filename="tri24.bmp"; if (surf1) surf1->Release(), surf1=0;

surf1=CreateSurface( filename, TRUE ); if (surf1==0) { FatalError("failed to load BMP"); return FALSE; } return TRUE; }


В приложении Bounce используется одна вспомогательная поверхность, содержимое которой определяется одним из двух BMP-файлов: 8- или 24-битным. Функция использует 8-битный файл, если текущий видеорежим является 8-битным, и 24-битный файл — в противном случае. В принципе один и тот же BMP-файл можно использовать в обоих сценариях, но это неразумно. Поскольку 8-битные файлы могут состоять лишь из 256 цветов, не стоит использовать их в режимах High и True Color. В свою очередь 24-битные изображения не пользуются палитрами и могут содержать до 16 миллионов цветов. Генерация 256-цветной палитры для такого изображения становится нетривиальной задачей. Функция CreateCustomSurfaces() определяет глубину пикселей текущего видеорежима с помощью функции DirectDrawWin::GetCurDisplayDepth(). На основании полученного результата выбирается имя BMP-файла.

Затем мы проверяем указатель surf1. Если его значение отлично от нуля, поверхность освобождается, а указатель обнуляется. Это происходит из-за того, что функция CreateCustomSurfaces() может вызываться неоднократно. Функция ActivateDisplayMode() вызывает ее при активизации видеорежима, поэтому приложение, которое за время работы меняет несколько видеорежимов, несколько раз вызовет CreateCustomSurfaces(). Если поверхность создавалась ранее, приведенный код освобождает ее.

Затем мы вызываем функцию CreateSurface(), чтобы создать поверхность и загрузить в нее содержимое BMP-файла. Функция CreateSurface() есть в интерфейсе DirectDrawSurface, но в данном случае используется версия из класса DirectDrawWin. Функция CreateSurface() загружает BMP-файл и создает поверхность, размеры и содержимое которой определяются изображением, хранящимся в заданном BMP-файле. CreateSurface() возвращает указатель на созданную поверхность, если все прошло нормально, и ноль — в противном случае.

Обратите внимание на то, что функция CreateSurface() получает два аргумента. Первый из них представляет собой имя загружаемого BMP-файла. Второй аргумент показывает, нужно ли устанавливать палитру BMP-файла. Для 24-битных BMP-файлов этот аргумент игнорируется.



Полезные хлопоты с палитрами


Не так давно в высокопроизводительных графических приложениях можно было сколько-нибудь приемлемо работать, лишь используя 8-битные режимы. Так как 8-битные режимы являются палитровыми, палитры были неотъемлемой частью жизни; программист был вынужден либо пользоваться ими, либо отказаться от попыток создания высокопроизводительных графических приложений.

Сейчас ситуация несколько изменилась. Видеорежимы High и True Color имеются на большинстве видеокарт и в полной мере поддерживаются DirectDraw. В этих режимах палитры не нужны, поэтому с ними оказывается легче работать. Вы можете создать собственное графическое изображение, загрузить его в программе и вывести на экран, не беспокоясь о цветовых конфликтах, нехватке элементов палитры или утилитах для работы с палитрой. В довершение всего аппаратное ускорение позволяет добиться впечатляющего быстродействия в этих режимах.

И все же перед тем, как выбрасывать поддержку 8-битных режимов из своего приложения, подумайте о преимуществах палитровых видеорежимов. 8-битные режимы очень трудно превзойти в области быстродействия. Они используют вдвое меньше памяти по сравнению с режимами High color и вчетверо меньше — по сравнению с True Color. Меньшие затраты означают увеличение свободной видеопамяти, а это в свою очередь ведет к повышению быстродействия.

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



Полноэкранные приложения


В полноэкранном режиме приложение DirectDraw получает исключительные права на работу с видеоустройствами. Полноэкранное приложение может свободно выбрать любой из режимов, поддерживаемых видеокартой, и управлять содержимым всей палитры. Кроме того, полноэкранные приложения могут выполнять переключение страниц. Благодаря этим преимуществам полноэкранные приложения превосходят оконные по скорости и гибкости.

Работа типичного полноэкранного приложения начинается с определения допустимых видеорежимов и активизации одного из них. Затем приложение создает переключаемую первичную поверхность с одним или несколькими вторичными буферами, а оставшуюся видеопамять использует для создания внеэкранных поверхностей. После истощения видеопамяти используется системная память. Подготовив очередной кадр во вторичном буфере, приложение выполняет операцию переключения страниц. Даже если первичная поверхность занимает всю доступную видеопамять, полноэкранные приложения опережают своих оконных собратьев благодаря способности к «настоящему» переключению страниц.

Так как полноэкранные приложения не ограничиваются текущим видеорежимом Windows, проблема с наличием свободной видеопамяти становится менее серьезной. При наличии всего 2 Мбайт видеопамяти можно сэкономить память за счет установки видеорежима с низким разрешением. Если будет обнаружено 4 Мбайт видеопамяти, ваше приложение сможет выбрать более требовательный видеорежим и при этом обеспечить хорошее быстродействие.

Полный контроль над содержимым палитры тоже оказывается полезным. Вы можете использовать все 256 элементов палитры и обойтись без переназначения цветов, обусловленного наличием 20 зарезервированных цветов Windows.



Получение данных о формате пикселей


Сведения о формате пикселей поверхности можно получить функцией GetPixelFormat() интерфейса DirectDrawSurface, в которой для передачи данных используется структура DDPIXELFORMAT. Функция GetPixelFormat() применяется так:


DDPIXELFORMAT format; ZeroMemory( &format, sizeof(format) ); format.dwSize=sizeof(format); surf->GetPixelFormat( &format );


Структура DDPIXELFORMAT содержит четыре поля, представляющих для нас интерес:

dwRGBBitCount

dwRBitMask

dwGBitMask

dwBBitMask

Поле dwRGBBitCount показывает глубину пикселей поверхности. Три оставшихся поля являются масками, определяющими, в каких битах пикселя хранятся данные красной, зеленой и синей составляющих. Например, типичные значения полей для поверхности High Color формата 5-6-5 приведены в табл. 5.1.

Три маски показывают, в каких позициях пикселя хранятся данные отдельных цветовых составляющих. С помощью этих масок можно корректно читать и записывать данные пикселя независимо от его формата.

Таблица 5.1. Типичные данные формата для 16-битных пикселей

ПолеЗначениеДвоичное значение
dwRGBBitCount
dwRBitMask
dwGBitMask
dwBBitMask
16
63488
2016
31
(неважно)
1111100000000000
0000011111100000
0000000000011111

В приведенном ниже коде маски используются для вычисления двух величин: начальной позиции каждой цветовой составляющей внутри пикселя (бит, с которого начинается составляющая, или стартовый бит), и количества бит для представления каждой цветовой составляющей. Значения этих величин приведены в табл. 5.2. Обратите внимание на то, что стартовый бит отсчитывается справа налево (старшие биты в двоичных величинах находятся слева).

Рассчитанные значения облегчают операции с пикселями. Стартовый бит показывает, на сколько позиций сдвигаются данные цветовой составляющей, а количество — сколько бит занимает составляющая в двоичной величине.

Таблица 5.2. Типичные данные формата для 16-битных пикселей

ПолеЗначениеДвоичное значениеСтартовый битКоличество бит
dwRBitMask
dwGBitMask
dwBBitMask
63488
2016
31
1111100000000000
0000011111100000
0000000000011111
11
5
0
5
6
5
<
br>

Однако до сих пор мы рассматривали лишь 16-битные пиксели. 8-битные пиксели нас не интересуют, но перед тем, как идти дальше, необходимо уделить внимание пикселям формата True Color. В табл. 5.3 приведены данные формата пикселей (в том числе две вычисленные величины для каждой цветовой составляющей) для типичного 24-битного формата.

Таблица 5.3. Типичные данные формата для 24-битных пикселей

ПолеЗначениеДвоичное значениеСтартовый битКоличество бит
dwRBitMask
dwGBitMask
dwBBitMask
16711680
65280
255
111111110000000000000000
000000001111111110000000
000000000000000011111111
16
8
0
8
8
8
Помните - данные в этих таблицах относятся к конкретной аппаратуре. Они представлены лишь для примера того, как могут выглядеть такие данные, а не как исчерпывающее руководство по форматам пикселей.




Получение данных от мыши


Хлопоты с инициализацией мыши и клавиатуры закончены, теперь можно получать от них данные. Функция DrawScene() (см. листинг 6.6) через указатели mouse и keyboard обращается к обоим устройствам и получает от них данные.

Листинг 6.6. Функция SmearWin::DrawScene()


void SmearWin::DrawScene() { static char key[256]; keyboard->GetDeviceState( sizeof(key), &key );

if ( key[DIK_ESCAPE] & 0x80 ) PostMessage( WM_CLOSE );

BOOL done=FALSE; while (!done) { DIDEVICEOBJECTDATA data; DWORD elements=1; HRESULT r=mouse->GetDeviceData( sizeof(data), &data, &elements, 0 ); if (r==DI_OK && elements==1) { switch(data.dwOfs) { case DIMOFS_X: x+=data.dwData; break; case DIMOFS_Y: y+=data.dwData; break; } } else if (elements==0) done=TRUE; }

BltSurface( primsurf, sphere, x, y, TRUE ); }


Функция DrawScene() сначала проверяет состояние клавиатуры функцией GetDeviceState() интерфейса DirectInputDevice. Если была нажата клавиша Escape, она посылает сообщение WM_CLOSE, что приводит к завершению приложения. О функции GetDeviceState() и проверке состояния клавиш рассказано в программе Qwerty, поэтому сейчас мы займемся кодом, относящимся к мыши. DrawScene() в цикле извлекает элементы буфера мыши. Для получения данных, а также для проверки отсутствия элементов при пустом буфере используется функция GetDeviceData() интерфейса DirectInputDevice.

Каждый элемент буфера представлен структурой DIDEVICEOBJECTDATA. Эта структура используется независимо от типа устройства, поэтому ее поля были сделаны универсальными. DirectInput определяет структуру DIDEVICEOBJECTDATA следующим образом:


typedef struct { DWORD dwOfs; DWORD dwData; DWORD dwTimeStamp; DWORD dwSequence; } DIDEVICEOBJECTDATA, *LPDIDEVICEOBJECTDATA;


Для мыши поле dwOfs определяет тип события. В DirectInput определены следующие константы, описывающие ввод от мыши:

DIMOFS_X

DIMOFS_Y

DIMOFS_Z

DIMOFS_BUTTON0

DIMOFS_BUTTON1

DIMOFS_BUTTON2

DIMOFS_BUTTON3

Программа Smear реагирует только на перемещение мыши по осям x и y, поэтому после вызова функции GetDeviceData() поле dwOfs сравнивается с константами DIMOFS_X и DIMOFS_Y.



Поле dwData определяет новые значения осевых координат и кнопок. Поскольку мы используем относительные значения, содержимое этого поля равно смещению по данной оси с момента получения последних данных. Следовательно, оно может быть и отрицательным. Поле dwData используется для обновления переменных x и y.

Поля dwTimeStamp и dwSequence содержат информацию о том, когда произошло данное событие. Поле dwTimeStamp определяет время в миллисекундах (о том, как интерпретируется эта величина, можно подробно узнать в описании функции Win32 GetTickCount()). Поле dwSequence определяет порядок наступления событий. События с меньшими номерами наступили раньше, однако несколько событий могут иметь одинаковые порядковые номера. Например, если мышь или рукоять джойстика смещается по диагонали, события для координат x и y будут иметь одинаковые номера.

Вернемся к функции DrawScene(). Цикл ввода извлекает элементы буфера до тех пор, пока буфер не опустеет. Этот цикл выглядит так:

while (!done) { DIDEVICEOBJECTDATA data; DWORD elements=1; HRESULT r=mouse->GetDeviceData( sizeof(data), &data, &elements, 0 ); if (r==DI_OK && elements==1) { switch(data.dwOfs) { case DIMOFS_X: x+=data.dwData; break; case DIMOFS_Y: y+=data.dwData; break; } } else if (elements==0) done=TRUE; }


Третий аргумент GetDeviceData() используется двояко. Значение, передаваемое функции, определяет количество элементов, извлекаемых из буфера. В нашем случае используется всего одна структура DIDEVICEOBJECTDATA, поэтому передается число 1. При возврате из функции это значение показывает количество полученных элементов.

Если вызов функции прошел успешно и значение elements осталось равным 1, значит, элемент буфера был прочитан, а поля dwOfs и dwData определяют тип события. Нулевое значение elements говорит о том, что буфер пуст и цикл завершается.

После извлечения всех элементов буфера остается лишь вывести поверхность в позиции, определяемой переменными x и y. Для этого используется функция BltSurface():

BltSurface( primsurf, sphere, x, y, TRUE );


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




Получение списка драйверов DirectDraw


Функция DirectDrawEnumerate() предназначена для составления списка доступных драйверов DirectDraw. Чаще всего обнаруживается всего один драйвер DirectDraw — тот, который управляет установленной видеокартой. Тем не менее в некоторых конфигурациях может присутствовать несколько видеоустройств. В таких случаях DirectDrawEnumerate() покажет отдельный драйвер для каждого видеоустройства, поддерживаемого DirectDraw.

Функция DirectDrawEnumerate() получает два аргумента: указатель на косвенно вызываемую (callback) функцию и указатель на данные, определяемые приложением, которые передаются этой функции при вызове. В нашем случае аргументами являются косвенно вызываемая функция DriverAvailable() и указатель на класс DirectDrawWin (this). Функция DriverAvailable() определяется так:


BOOL WINAPI DirectDrawWin::DriverAvailable(LPGUID guid, LPSTR desc, LPSTR name, LPVOID p) { DirectDrawWin* win=(DirectDrawWin*)p;

if (win->totaldrivers >= MAXDRIVERS) return DDENUMRET_CANCEL;

DriverInfo& info=win->driver[win->totaldrivers];

if (guid) { info.guid=(GUID*)new BYTE[sizeof(GUID)]; memcpy(info.guid, guid, sizeof(GUID)); } else info.guid=0;

info.desc=strdup(desc); info.name=strdup(name);

win->totaldrivers++;

return DDENUMRET_OK; }


Сначала указатель на данные, определяемые приложением (p), преобразуется в указатель на класс DirectDrawWin (win). Поскольку функция DriverAvailable() объявлена как статическая (косвенно вызываемые функции обязаны быть статическими), на нее в отличие от обычных функций класса не распространяются правила автоматического доступа; соответственно доступ к переменным и функциям класса приходится осуществлять через указатель win.

DirectDraw вызывает функцию DriverAvailable() один раз для каждого обнаруженного драйвера. При каждом вызове передаются три информационных объекта: GUID, описание и имя. GUID (глобально-уникальный идентификатор) однозначно идентифицирует драйвер. Описание и имя представляют собой строки для неформальной идентификации драйвера. Функция DriverAvailable() сохраняет сведения о каждом драйвере в массиве с именем driver и отслеживает количество драйверов в переменной totaldrivers. Наконец, функция DriverAvailable() возвращает DDNUMRET_OK, показывая, что перечисление драйверов должно продолжаться. При получении кода возврата DDENUMRET_CANCEL DirectDraw прекращает перечисление драйверов.

Если была установлена библиотека DirectX и в системе присутствует видеоустройство, поддерживаемое DirectDraw, то будет обнаружен по крайней мере один драйвер DirectDraw. Этот драйвер соответствует первичному видеоустройству (тому, что используется Windows). Его GUID равен нулю, строка описания содержит текст «Primary Display Driver», а строка имени — «display». При перечислении дополнительных драйверов используются нормальные значения GUID. Строки описаний и имен зависят от типов видеоустройств и версий драйверов.



Поток ввода


Поток ввода обладает более узкой специализацией по сравнению с основным потоком. Он должен делать следующее:

обнаруживать ввод от мыши;

обновлять курсор;

синхронизироваться с основным потоком;

обрабатывать сигнал завершения, полученный от основного потока.

Для получения ввода от мыши могут применяться две схемы: опрос и оповещение. Опрос плохо подходит для нашего случая, потому что поток ввода постоянно остается активным, даже если пользователь не работает с мышью. С другой стороны, если поток ввода блокируется до поступления новых данных от мыши, он почти не расходует лишнего процессорного времени. С помощью имеющегося в DirectInput механизма оповещения можно заблокировать поток ввода до тех пор, пока DirectInput не сообщит о поступлении новых данных.

После получения сигнала поток ввода извлекает новые данные и обновляет курсор одним из двух способов, рассмотренных выше. Независимо от того, какой способ будет использован, обновление курсора необходимо синхронизировать с основным потоком, чтобы потоки не пытались обратиться к первичной поверхности одновременно.

Наконец, поток ввода отвечает за свое завершение. При получении сигнала от основного потока он должен прекратить работу.


br>Функция MouseThread() имеет один параметр — значение, передаваемое функции AfxBeginThread() при создании потока (см. листинг 7.3). Мы передавали указатель this, поэтому сейчас сможем присвоить его значение указателю на класс CursorWin (переменная win). В функции MouseThread() указатель win будет использоваться для доступа к членам класса CursorWin.

Функция MouseThread() в цикле выполняет блокировку по двум событиям. Класс CMultiLock

позволяет блокироваться как по событиям от мыши, так и по событию завершения потока. Фактическая блокировка выполняется функцией CMultiLock::Lock(). По умолчанию функция Lock() блокирует поток до установки всех (в данном случае  - двух) заданных событий. Мы изменяем это поведение и передаем FALSE в качестве второго аргумента Lock(), показывая тем самым, что функция должна снимать блокировку при установке хотя бы одного из этих событий.

Когда любое из двух событий переходит в установленное состояние, функция Lock()

завершается, и мы проверяем код возврата. Если выясняется, что было установлено событие завершения потока (обозначенное константой quit_event_index), мы выходим из функции MouseThread(), тем самым завершая поток. В противном случае активизация потока вызвана событием мыши, поэтому мы переходим к обработке новых данных.

Однако сначала необходимо захватить критическую секцию с помощью объекта critsection. Для получения данных нам придется обращаться к очереди событий от кнопок мыши и к первичной поверхности, поэтому выполнение этого кода следует синхронизировать с основным потоком.

Мы в цикле получаем данные от объекта DirectInputDevice, представляющего мышь, с помощью функции GetDeviceData(). Если получены данные о перемещении мыши, происходит обновление переменных curx и cury. Если получены данные о нажатии кнопок, они заносятся в очередь событий.

Когда цикл получения данных завершается (поскольку в буфере не остается элементов), мы проверяем переменные curx и cury

и убеждаемся, что курсор не вышел за пределы экрана (вместо того чтобы писать код частичного отсечения курсора, мы выбираем простой путь и требуем, чтобы курсор всегда полностью оставался на экране).



Наконец, мы проверяем новое положение курсора. Если перемещение курсора не обнаружено, критическая секция освобождается, а объект CMultiLock

снова используется для блокировки по обоим событиям. Если курсор переместился в другое положение, мы вызываем одну из двух функций обновления курсора в зависимости от того, перекрывается ли старая область курсора с новой. Если области перекрываются, вызывается функция UpdateCursorComplexCase(); в противном случае вызывается функция UpdateCursorSimpleCase().

Начнем с более простой функции UpdateCursorSimpleCase() (см. листинг 7.6).

Листинг 7.6. Функция UpdateCursorSimpleCase()

BOOL CursorWin::UpdateCursorSimpleCase(int curx, int cury, int oldcurx, int oldcury) { RECT src; HRESULT r;

//------ Блиттинг 1: стирание старого курсора ---------- r=primsurf->BltFast( oldcurx, oldcury, cursor_under, 0, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 1 failed\n"); CheckResult(r); }

//------ Блиттинг 2: сохранение области под новым курсором ------ src.left=curx; src.top=cury; src.right=curx+cursor_width; src.bottom=cury+cursor_height; r=cursor_under->BltFast( 0, 0, primsurf, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 2 failed\n"); CheckResult(r); }

//------ Блиттинг 3: рисование нового курсора ---------- r=primsurf->BltFast( curx, cury, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 3 failed\n"); CheckResult(r); }

return TRUE; }


С помощью трех последовательных вызовов функции BltFast() интерфейса DirectDrawSurface, функция UpdateCursorSimpleCase() стирает существующий курсор, сохраняет область под новым курсором и рисует новый курсор.

В UpdateCursorComplexCase() функция BltFast()

вызывается пять раз. Два дополнительных блиттинга предназначены для копирования обновляемой части первичной поверхности на вспомогательную поверхность (cursor_union) и обратно. Функция UpdateCursorComplexCase() приведена в листинге 7.7.

Листинг 7.7.


Функция UpdateCursorComplexCase()

BOOL CursorWin::UpdateCursorComplexCase( int curx, int cury, int oldcurx, int oldcury) { RECT src; HRESULT r; int unionx=min(curx, oldcurx); int uniony=min(cury, oldcury); int unionw=max(curx, oldcurx)-unionx+cursor_width; int unionh=max(cury, oldcury)-uniony+cursor_height;

//----- Блиттинг 1: копирование объединяющего прямоугольника // во вспомогательный буфер -------- src.left=unionx; src.top=uniony; src.right=unionx+unionw; src.bottom=uniony+unionh; r=cursor_union->BltFast( 0, 0, primsurf, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 1 failed\n"); CheckResult(r); }

//------ Блиттинг 2: стирание старого курсора // во вспомогательном буфере --------- r=cursor_union->BltFast( oldcurx-unionx, oldcury-uniony, cursor_under, 0, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 2 failed\n"); CheckResult(r); } //------ Блиттинг 3: сохранение области под новым курсором ----- src.left=curx-unionx; src.top=cury-uniony; src.right=src.left+cursor_width; src.bottom=src.top+cursor_height;r r=cursor_under->BltFast( 0, 0, cursor_union, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 3 failed\n"); CheckResult(r); }

//------ Блиттинг 4: рисование нового курсора // во вспомогательном буфере --------- r=cursor_union->BltFast( curx-unionx, cury-uniony, cursor, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 4 failed\n"); CheckResult(r); }

//------- Блиттинг 5: копирование вспомогательного буфера // на первичную поверхность -------- src.left=0; src.top=0; src.right=unionw; src.bottom=unionh; r=primsurf->BltFast( unionx, uniony, cursor_union, &src, DDBLTFAST_WAIT ); if (r!=DD_OK) { TRACE("Blt 5 failed\n"); CheckResult(r); } return TRUE; }

Пользуясь одной из этих двух функций, поток ввода обновляет курсор. При этом удается избежать мерцания и разрушения текущего изображения на первичной поверхности.


Потоки и процессы


Сознаете вы это или нет, но вы уже знакомы с потоками и процессами. Каждый раз при запуске программы создается новый процесс. Процесс обеспечивает программу всем, что ей нужно для работы, включая один поток (thread). Этот стандартный поток (также называемый основным потоком — primary thread) используется для выполнения кода программы. Основной поток типичного процесса начинает работу с точки входа (для Windows-программ это функция WinMain()) и продолжает выполняться в соответствии со всеми циклами, условными операторами и вызовами функций. Основной поток завершается вместе с завершением процесса.

Однако ничто не ограничивает процесс одним потоком. Средства MFC или Win32 позволяют создавать дополнительные потоки, которые обычно используются для выполнения фоновых задач. Эти дополнительные потоки (иногда называемые рабочими потоками) работают независимо от основного потока (а также друг от друга). Каждый поток обладает собственным стеком, но системные ресурсы (такие, как файлы и динамическая память) используются потоками совместно.



Поверхности


Поверхностью (surface) в DirectDraw называется прямоугольная область памяти, обычно содержащая графические данные. Блок памяти, изображающий поверхность, может находиться как в системной, так и в видеопамяти. Хранение поверхностей в видеопамяти обычно повышает скорость работы программы, поскольку большинство видеокарт не может обращаться к системной памяти напрямую.

Поверхности делятся на несколько типов. Простейшими являются внеэкранные (off-screen) поверхности. Внеэкранная поверхность может находиться как в видеопамяти, так и в системной памяти, но не отображается на экране. Такие поверхности обычно используются для хранения спрайтов и фоновых изображений.

Первичная (primary) поверхность, напротив, представляет собой участок видеопамяти, отображаемой на экране. Любая программа DirectDraw, обеспечивающая графический вывод, имеет первичные поверхности. Первичная поверхность должна находиться в видеопамяти.

Первичные поверхности часто бывают составными (complex), или, что то же самое, переключаемыми (flippable). Переключаемая поверхность может участвовать в переключении страниц - операции, при которой содержимое всей поверхности мгновенно отображается на экране с помощью специальных аппаратных средств. Переключение страниц используется во многих графических программах как с поддержкой DirectDraw, так и без, поскольку оно обеспечивает очень гладкую анимацию и устраняет мерцание. Переключаемая первичная поверхность на самом деле состоит из двух поверхностей, одна из которых отображается на экране, а другая — нет. Невидимая поверхность называется вторичным буфером (back buffer). При переключении страниц поверхности меняются местами: та, которая была вторичным буфером, отображается на экране, а та, что ранее отображалась, превращается во вторичный буфер.

Как внеэкранные, так и первичные поверхности делятся на две разновидности: палитровые (palettized) и беспалитровые (non-palettized). Палитровая поверхность вместо конкретных значений цветов содержит индексы в цветовой таблице, которая называется палитрой. В DirectDraw палитровыми являются только 8-битные поверхности. Поверхности с глубиной пикселей, равной 16, 24 и 32 битам, являются беспалитровыми. Вместо индексов в них хранятся фактические значения цветов.

Поскольку в каждом пикселе беспалитровой поверхности находятся реальные цветовые данные, необходимо знать, в каком формате хранятся отдельные пиксели поверхностей. Формат пикселя описывает способ хранения красной, зеленой и синей (RGB) составляющих. Он зависит от глубины пикселей, видеорежима и аппаратной архитектуры. Форматы пикселей подробно рассматриваются в главе 5.


Ситуации, в которой DirectDraw сможет заблокировать поверхность без остановки системы, нигде не описаны, поэтому нет никаких гарантий, что ваша просьба будет удовлетворена. Если это не удастся сделать, поверхность блокируется стандартным способом.

Для прямого доступа к поверхности нужно знать формат ее пикселей. Этот формат определяет способ хранения цветовых данных каждого пикселя. Он может изменяться в зависимости от видеоустройства и даже от видеорежима. Форматы пикселей особенно сильно различаются для поверхностей High Color (16-битных).

При прямом доступе к памяти поверхности необходимо также знать значение шага поверхности (surface stride). Шагом называется объем памяти, необходимый для представления горизонтальной строки пикселей. С первого взгляда кажется, что шаг поверхности совпадает с ее шириной, но на самом деле это разные параметры. Шаг поверхности, как и формат пикселей, зависит от конкретного видеоустройства.

В этой главе мы рассмотрим и решим все эти проблемы. Мы начнем с изучения форматов пикселей, а затем посмотрим, как написать код для работы с любыми типами поверхностей, независимо от глубины и формата пикселей. Затем мы изучим формат BMP-файлов, при этом основное внимание будет уделяться загрузке BMP-файла на поверхность. Главу завершает программа BmpView, предназначенная для просмотра графических файлов формата BMP (если вас интересует только процесс загрузки растровых изображений на поверхности, обращайтесь к заключительному разделу этой главы).




особенно начинающие) любят задавать вопросы


Программисты ( особенно начинающие) любят задавать вопросы типа: «Скажи, на чем ты пишешь…?» Когда-то этот вопрос выглядел вполне логично. Компиляторы, отладчики, серверы, системы управления базами данных и все остальное только-только выходило из каменного века. Программные инструменты разительно отличались друг от друга по качеству и возможностям. Стоило сделать ставку на неудачный инструментарий, и работа становилась излишне тяжкой, а качество результата— низким.

Сегодня стал актуальным другой вопрос: «А чего стоишь ты сам?» Благодаря непрерывной конкуренции современные средства разработчика стали невероятно мощными и качественными, так что среднему программисту вряд ли удастся выжать из них все возможное. Скорее всего, вы спасуете намного раньше, чем ваш инструментарий — если только не узнаете о нем абсолютно все и не доведете свое мастерство программиста до подлинного совершенства.

Книги этой серии предназначены для углубленного изучения программных инструментов. В них рассматриваются нетривиальные возможности, которые невозможно описать в простейшем учебнике. Полноценные проекты заставляют читателя мыслить на уровне эксперта — напрягать серое вещество, лежащее в основе всего, что мы называем «мастерством».

Конечно, это не единственный путь — например, можно добросовестно набивать шишки обо все острые углы новых технологий и наобум пробовать все подряд, пока что-нибудь не заработает. А можно воспользоваться опытом наших авторов, которые уже прошли стадию обучения и попутно сделали кое-какие заметки на память. Мы тщательно отобрали темы, авторов и методику изложения, чтобы читатель не путался в ненужных вступлениях или редких технологиях, которые ему все равно не понадобятся.

Наша главная цель — поднять ваше мастерство настолько, насколько вы сами захотите. Классные инструменты у вас уже есть, осталось лишь стать классным программистом.

Джефф Дантеманн

Представление данных в видеофайлах


Форматы видеофайлов (такие как AVI) фактически поддерживают два уровня сжатия. Различные алгоритмы обеспечивают сжатие отдельных кадров; кроме того, некоторые кадры представляются не в виде самостоятельных изображений, а в виде изменений, внесенных в предыдущие кадры.

Это объясняется тем, что типичный видеоролик состоит из похожих изображений. Например, если в нем показано, как человек идет по комнате, то фигура человека будет изменяться от кадра к кадру, но остальная часть (комната), скорее всего, останется практически неизменной. Следовательно, такой видеоролик можно представить в виде первого кадра и цепочки последующих изменений.

Кадры видеоролика с полным изображением называются ключевыми кадрами

(key frames). Видеоролик может содержать любое количество ключевых кадров, в зависимости от того, насколько часто и сильно меняется изображение. Ключевые кадры особенно часто появляются при переходах между сценами, эффектах «растворения» и перемещении съемочной камеры. Ключевые кадры чрезвычайно важны при чтении видеоролика; без них промежуточные кадры теряют смысл. Неключевой кадр тоже можно вывести, но изображение, скорее всего, будет нарушено, потому что такой кадр представляет собой лишь изменение предыдущего изображения, а не самостоятельное изображение.

Открытый видеофайл обычно называется потоком. Как мы увидим в следующем разделе, посвященном Video For Windows API, концепция потока тесно интегрирована с Video For Windows.



Прекомпилированные заголовки


Лично я— горячий поклонник прекомпилированных заголовков. Настроить их нетрудно, а компиляция проектов Visual C++ проходит намного быстрее обычного. Если вам придется компилировать большой проект без прекомпилированных заголовков, то к концу компиляции вы успеете забыть, что же именно изменилось в вашей программе.

Одна из разрекламированных особенностей Visual C++, которую я ждал с особым нетерпением, — возможность задания параметров проекта из пользовательских AppWizard. Это позволило бы мне написать AppWizard, который помимо генерации исходных текстов мог бы настраивать конфигурацию новых проектов. Особенно сильно я рассчитывал на настройку и включение прекомпилированных заголовков.

Такая возможность была предусмотрена для стандартного MFC AppWizard из комплекта Visual C++; впрочем, она присутствовала и в предыдущих версиях. После нововведений в Visual C++ 5.0 то же самое могут (теоретически) сделать и разработчики нестандартных AppWizard.

Первая проблема состоит в том, что справка, документирующая эту новую возможность, была написана для Visual Basic и потому оказалась практически бесполезной для программистов на C++ (несомненно, мы имеем дело с побочным эффектом многоязыковой ориентации Developer Studio). Вторая проблема — в том, что (в соответствии с документацией) уровень доступа, необходимый для оптимальной конфигурации прекомпилированных заголовков, вам не предоставляется. Выяснилось, что с проектами можно работать на уровне конфигураций, но не на уровне файлов. До меня доходили слухи, что такая возможность все-таки есть, но она не документирована; однако мои поиски в заголовочных файлах ни к чему не привели.

Все проекты, созданные на базе прилагаемого к этой книге AppWizard, обладают одним общим недостатком — отсутствием полноценной поддержки прекомпилированных заголовков (точнее, это относится не к программам на CD-ROM, а к новым проектам, сгенерированным в DirectDraw AppWizard). Все, что я могу вам посоветовать, — включить эту возможность после создания нового проекта (сделать это несложно, но все-таки лучше, если бы это происходило автоматически).
Сейчас я расскажу о том, как настроить прекомпилированные заголовки для только что созданного проекта с именем Sample.



Рис. А.13. Вкладка C/C++ окна Project Settings

После того, как вы создадите новый проект с помощью DirectDraw AppWizard, выполните команду Project|Settings и перейдите на вкладку C/C++. Затем выберите из списка Category строку Precompiled Headers. По умолчанию устанавливается переключатель Automatic use of Precompiled Headers, однако автоматическая настройка прекомпилированных заголовков намного уступает ручной. Стандартный вид вкладки C/C++ окна Project Settings изображен на рис. А.13.

Выберите из расположенного слева списка Setting For строку All Configurations. Благодаря этому мы сможем разрешить применение прекомпилированных заголовков как в окончательной, так и в отладочной конфигурации. Теперь установите переключатель Use precompiled header file и введите в поле Through header строку headers.h — имя файла, в котором будут храниться данные прекомпилированных заголовков.

Теперь раскройте в иерархическом дереве слева узел Sample — появятся три узла следующего уровня. Раскройте узел Source Files. Выберите файл Headers.cpp, один раз щелкнув на его строке. Затем установите переключатель Create precompiled header file и введите в поле Through header строку headers.h. Диалоговое окно после внесения всех необходимых изменений изображено на рис. А.14.



Рис. А.14. Вкладка C/C++ окна Project Settings после внесения изменений

Наконец, нажмите кнопку OK и откомпилируйте проект. Обратите внимание на то, что сначала компилируется файл Headers.cpp. На этой стадии Visual C++ создает прекомпилированный заголовочный файл, что требует некоторого времени. Однако последующие модули компилируются быстрее, потому что заголовочные файлы уже были откомпилированы ранее. Также обратите внимание на то, что файл Headers.cpp

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

И последнее замечание. Лучшими кандидатами для прекомпиляции являются заголовочные файлы MFC и DirectX (именно они включены в файл Headers.h

во всех проектах на CD-ROM, а также в тех, что создаются DirectDraw AppWizard). Тем не менее, если эти файлы изменятся (например, из-за выхода новой версии Visual C++ или DirectX), вы должны обновить прекомпилированные заголовки командой Rebuild All.


А. Информация для разработчиков


Отладка

Несколько замечаний о VisualC++

Полезные советы

Microsoft и DirectX SDK

Ответы на вопросы

Вот и все - книга подходит к концу. Однако наше внимание было настолько приковано к DirectDraw (и DirectInput), что некоторые важные темы так и не были рассмотрены. Например, в DirectDraw есть несколько досадных недочетов, о которых необходимо знать (если вы еще не успели столкнуться с ними). Свои недостатки есть и у Visual C++. Кроме того, некоторые особенности программного кода на CD-ROM могут представлять для вас интерес. Во время работы над программами я отобрал ряд полезных советов. Наконец, у меня есть несколько общих замечаний, которые не удалось привязать к основному тексту. Все эти темы рассматриваются в приложении.

Начнем с разговора об отладке. Конкретнее, мы изучим несколько способов отладки полноэкранных приложений DirectDraw (иногда это превращается в сплошные мучения). После этого мы поговорим о Visual C++, а также об ошибках и раздражающих недостатках DirectDraw — если не знать о них, ваш прогресс может надолго остановиться.



Проблемы


Отладить полноэкранное приложение намного сложнее, чем любое другое, и для этого есть несколько причин. Самая очевидная из них— раз приложение занимает весь экран, вы не сможете изменить точки прерывания, просмотреть код программы или значения переменных. Дело усложняется переключением страниц. Если программа должна вывести проверочное сообщение или отладочное окно, нельзя гарантировать, что оно действительно появится на экране, потому что Windows может вывести информацию во вторичном буфере. И даже если вам повезет и вы сможете увидеть это окно (при использовании буферизации ваши шансы — 50 на 50), вероятно, его все равно не удастся толком разглядеть из-за различий между текущей и системной палитрой.

Следовательно, многие традиционные механизмы и методики отладки не работают для DirectDraw. Интегрированный отладчик Visual C++ практически бесполезен, а пользоваться стандартными отладочными макросами типа ASSERT() или VERIFY() оказывается рискованно.



Проблемы с диалоговыми окнами


Работать с диалоговыми окнами Windows в полноэкранном приложении оказывается не так уж просто. Разумеется, полноэкранному приложению нужен интерфейс, но стоит ли для этого использовать знакомый интерфейс Windows — вопрос спорный. Решение этой проблемы можно выбрать из трех основных вариантов:

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

восстанавливать рабочий стол каждый раз, когда потребуется вывести диалоговое окно;

создать нестандартное диалоговое окно и управляющие элементы DirectDraw.

В первом варианте вам придется самостоятельно управлять переключением страниц и палитрами с учетом Windows GDI. GDI не поддерживает DirectDraw, так что независимо от того, какая страница видеопамяти отображается в данный момент, диалоговые окна GDI всегда выводятся на «настоящей» первичной поверхности, или поверхности GDI. В беспалитровых режимах вывод GDI выглядит правильно и без вмешательства с вашей стороны, но в палитровых режимах отсутствие поддержки DirectDraw в GDI дает о себе знать — GDI продолжает выводить диалоговые окна в системной палитре Windows, не подозревая о том, что в данный момент может действовать другая палитра. Решение зависит от требований, предъявляемых приложением к палитре.

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

Второй вариант — восстанавливать видеорежим и рабочий стол Windows перед отображением диалогового окна. Он встречается в некоторых коммерческих продуктах; например в игре MechWarrior 2 фирмы Activision для заставок, воспроизведения видеороликов и вступительного инструктажа используется стандартный интерфейс Windows. Затем, с началом миссии, игра берет на себя все управление видеокартой и не пользуется никакими интерфейсными компонентами Windows. После завершения миссии поверхность рабочего стола снова восстанавливается.
В этой игре данная методика работает неплохо.

Версия MechWarrior 2, о которой я говорю, проектировалась для чипов 3Dfx. Для видеоустройств, построенных на таких чипах, вывод диалоговых окон в DirectDraw невозможен, потому что 3Dfx являются вторичными видеоустройствами, и GDI ничего не знает об их существовании. Поэтому команда разработчиков Activision не могла выбрать первый вариант и отображать диалоговые окна в DirectDraw.

Третья стратегия (создание нестандартных интерфейсов на базе DirectDraw) оказывается самой трудоемкой. Разработка нестандартных управляющих элементов давно превратилась в отдельную отрасль программной индустрии, и мало кто из разработчиков позволяет себе тратить время на создание кнопок, ползунков и списков, когда с приложением и без того хватает хлопот. С другой стороны, этот вариант позволяет создать интерфейс, спроектированный специально для вашего приложения. Теоретически этот интерфейс может быть столь же привлекательным и впечатляющим, как и само приложение.

В программе BmpView используется первый вариант. Перед выводом диалогового окна на
рис. 5.7 мы отображаем поверхность GDI и восстанавливаем системную палитру.


Программа AviPlay


Пора браться за программирование. Программу AviPlay, как и все остальные программы, рассматриваемые в этой книге, можно найти на CD-ROM.

Программа AviPlay использует Video For Windows для открытия и воспроизведения AVI-файлов на поверхностях DirectDraw. Она позволяет выбрать любой AVI-файл и задать видеорежим для воспроизведения ролика. Диалоговое окно для выбора файла изображено на рис.8.1.


Рис. 8.1. Диалоговое окно для выбора AVI-файла в программе AviPlay

Если вы еще не успели опробовать программу AviPlay и не обнаружили этого сами, я хочу указать на несколько вещей, которые эта программа не умеет делать. Например, она не воспроизводит аудиопотоки из выбранного AVI-файла, а лишь извлекает и воспроизводит графическую часть файла.

Кроме того, программа не пытается управлять частотой вывода кадров; все кадры извлекаются и воспроизводятся с максимальной скоростью. В каждом AVI-файле присутствуют хронометрические данные, которые используются всеми полноценными программами для воспроизведения AVI-файлов.

Еще одна полезная возможность, которая бы пригодилась в программе AviPlay, — поддержка 16- и 24-битных видеорежимов. В представленном варианте всегда применяются 8-битные режимы независимо от глубины пикселей воспроизводимого ролика.



Программа BmpView


На основе полученных знаний мы напишем приложение DirectDraw для просмотра BMP-файлов. Программа BmpView отображает диалоговое окно, в котором пользователь выбирает BMP-файл. Затем она выводит список всех видеорежимов, пригодных для просмотра выбранного изображения. Если выбрать видеорежим и нажать кнопку Display, программа BmpView переходит в заданный режим и отображает содержимое BMP-файла. Если изображение не помещается на экране, его можно прокрутить с помощью клавиш стрелок, Home, End, Page Up и Page Down. Диалоговое окно для выбора файла изображено на рис. 5.7.


Рис. 5.7. Диалоговое окно для выбора файла в программе BmpView

Обратите внимание на то, что в диалоговом окне отображаются размеры выбранного файла, а глубина пикселей определяет, какие видеорежимы могут использоваться для его отображения. На рисунке выбрано 8-битное изображение, поэтому в список включены только 8-битные режимы. Если выбрать 24-битное изображение, то список состоял бы только из беспалитровых режимов.

Наше знакомство с программой BmpView затрагивает следующие вопросы:

загрузка изображений из BMP-файлов;

прямой доступ к памяти поверхности;

прокрутка больших поверхностей;

работа с диалоговыми окнами Windows в DirectDraw.

Первые два вопроса мы уже обсудили, осталось лишь рассмотреть код. Хотя два последних вопроса и не имеют прямого отношения к теме, о них тоже стоит поговорить.



Программа Bumper


Для проверки алгоритма мы напишем демонстрационную программу. Программа Bumper выполняет отображение и анимацию восьми спрайтов. Как я упоминал, при столкновении спрайты разлетаются в противоположных направлениях. Программа Bumper изображена на рис. 9.4.

Восемь спрайтов, показанных на рисунке, представлены четырьмя разными поверхностями — по каждой поверхности создаются два спрайта. Исходные векторы направления, по которым перемещаются спрайты, определяются случайным образом. В начале своей работы программа «раскручивает» генератор случайных чисел, чтобы результаты ее работы не были всегда одинаковыми. При нажатии клавиши пробела векторы направления пересчитываются заново. Код программы Bumper рассматривается в следующих разделах.