Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

eManual.ru - электронная документация
ПрограммыЖелезоДрайверыХостингЭнциклопедия рекламы

Глава 12. Интерфейс DirectDraw


151.jpg

Добро пожаловать в «машинное отделение»! В этой главе мы рассмотрим интерфейс DirectDraw, который открывает перед нами низкоуровневый доступ к видеоадаптеру. Мы узнаем, для чего нужен данный интерфейс, как он устроен и что с ним делать. Приложение для этой главы находится в каталоге DDEval.

Что такое DirectDraw?


Библиотека DirectDraw предоставляет единый программный интерфейс для работы с различными видеоадаптерами. Но ведь подобный интерфейс, Microsoft Windows Graphics Device Interface (GDI), существовал и ранее? Вы совершенно правы. Главное отличие между DirectDraw и GDI заключается в том, что DirectDraw позволяет работать непосредственно с видеоадаптером, a GDI — наоборот, ограждает вас от этого! Возможно, сказанное не совсем справедливо по отношению к GDI — интерфейс проектировался для создания переносимых приложений, а о какой переносимости можно говорить, если кто угодно как угодно развлекается с видеоадаптером?

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

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

Чтобы вам было проще принять решение, мы кратко рассмотрим наиболее интересные возможности DirectDraw на примере несложного приложения. Это поможет вам в дальнейшем самостоятельно экспериментировать с DirectDraw.

Архитектура DirectDraw


На рис. 12-1 изображена слегка упрощенная архитектура GDI и DirectDraw с точки зрения приложения, работающего с трехмерными объектами.

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

• GDI.

• OpenGL.

• Абстрактный режим Direct3D.

• DirectDraw.

152.jpg

Рис. 12-1. Архитектуры GDI и DirectDraw

Рисование в GDI


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

Рисование в OpenGL


Для полноты картины я включил в наше рассмотрение механизм визуализации OpenGL. OpenGL входит в Win32 и позволяет создавать переносимые трехмерные приложения, обходясь без самостоятельных вычислений глубины и т. д. Язык OpenGL поддерживается на большом количестве платформ, что повышает переносимость вашего приложения. Качество реализации OpenGL отличается на разных платформах, так что производительность приложения также может изменяться. Поскольку реализация OpenGL в Microsoft Windows 95 использует прослойку Direct3D, ее производительность оказывается лучше, чем в ряде других систем.

Рисование в абстрактном режиме Direct3D


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

Рисование в DirectDraw


Путь, который мы рассмотрим сейчас, идет от приложения прямо к прослойке DirectDraw/Direct3D. Особый интерес представляет ветка DirectDraw, которая идет вниз, к видеоадаптеру, через HAL (прослойка абстрактной аппаратуры, Hardware Abstraction Layer) и HEL (прослойка эмуляции аппаратуры, Hardware Emulation Layer). Работа только с сервисом DirectDraw в определенном отношении напоминает обращение с GDI, поскольку немалую часть вычислении вам придется выполнять самостоятельно. Тем не менее если вы уверены в своих силах, то этот путь теоретически позволит добиться наилучшей производительности, поскольку перед вами открывается почти прямой доступ к видеоадаптеру. Если вы собираетесь перенести в Windows уже существующий механизм визуализации, то вам следует идти именно этим путем.

На рис. 12-1 компоненты DirectDraw и Direct3D изображены в одной общей рамке. Между ними существует немало сходства, так что в условном изображении архитектуры их можно разместить вместе. Тем не менее они все же отличаются между собой, и в этой главе мы будем рассматривать только DirectDraw. О Direct3D рассказывается в главе 13.

Прослойка абстрактной аппаратуры (HAL)


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

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

Прослойка эмуляции аппаратуры (HEL)


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

Компоненты DirectDraw


Архитектуру DirectDraw можно рассмотреть и с несколько иной точки зрения, как показано на рис. 12-2.

В этом случае DirectDraw предоставляет интерфейс для работы с видеопамятью и палитрой. Я изобразил на рисунке палитру, поскольку 256-цветные дисплеи с палитрой продолжают оставаться самыми распространенными. Если ваша видеосистема работает с 16-, 24- или 32-битной кодировкой пикселей, то палитра не понадобится, и схема несколько упрощается. Если временно не обращать внимания на палитру, мы увидим, что приложение должно располагать средствами для доступа к видеопамяти и перемещения данных в ней.

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

Если учесть, что HEL обеспечивает выполнение блитовых операций даже в том случае, если они не поддерживаются на аппаратном уровне, то становится понятно, почему на рис. 12-2 нет аппаратного блиттера. Приложение должно работать лишь с различными областями видеопамяти и, возможно, с палитрой.

Для приложения с трехмерными объектами схема выглядит более сложно, однако мы рассмотрим соответствующие отличия в следующей главе. А сейчас давайте обсудим различные компоненты DirectDraw и способы управления ими.

Поверхности в видеопамяти


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

153.jpg

Рис. 12-2. Компоненты DirectDrow

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

К одним поверхностям могут присоединяться другие. Взгляните на рис. 12-2. Вторичный буфер присоединен к первичному, в этом случае ими легче управлять, поскольку вам нужно хранить указатель лишь на первичный буфер. Указатель на вторичный буфер всегда можно получить из списка присоединенных поверхностей первичного буфера. При работе с трехмерными объектами иногда бывает необходимо отводить поверхность под Z-буфер, ее тоже удобно присоединить к первичному буферу. Кроме того, поверхность можно выделить под альфа-буфер и также присоединить ее к другой поверхности.

Поверхность описывается структурой DDSURFACEDESC, в которой содержатся поля для высоты поверхности, ее ширины и т. д. Кроме того, в нее входит структура DDPIXELFORMAT, описывающая формат отдельных пикселей. Пиксели могут быть заданы в виде RGB-тройки, индекса палитры, YUV-значения или в каком-нибудь другом формате, который поддерживается вашим видеоадаптером. Количество бит на пиксель изменяется от 1 до 32. Для поверхностей, где число бит на пиксель составляет 16 и менее, цветовые составляющие (например, красная, зеленая и синяя) задаются в виде масок, для которых следует выполнить операцию поразрядного AND со значением конкретного пикселя.

Чтобы получить доступ к поверхности, необходимо заблокировать ее и получить указатель. Завершив работу с поверхностью, вы разблокируете ее. Блокировка нужна для доступа к поверхности в режиме чтение/запись; она также помогает организовать взаимодействие с аппаратным блиттером и т. д. Следовательно, перед тем как пытаться получить доступ к поверхности, необходимо знать о том, кто ее использует. Пример будет приведен немного ниже, в разделе «Тестирование прямого доступа к пикселям» на стр. 296.

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

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

Палитры


Для описания цветов в поверхностях с 1-, 4- и 8-битной кодировкой пикселей применяются палитры. В DirectDraw поддерживаются 4- и 8-битные палитры, которые содержат 16 и 256 цветов соответственно. Палитра также может представлять собой набор индексов в другой палитре. Если вам приходилось работать с палитрами в Windows, то и палитры DirectDraw покажутся хорошо знакомыми. Если же вы привыкли «играть» с цветовыми таблицами видеоадаптера, то вам, вероятно, понравится гибкость работы с палитрами DirectDraw. Создание палитр рассматривается ниже, на стр. 298.

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

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

Ограничители


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

154.jpg

Рис. 12-3. Работа с буферами в полноэкранном режиме

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

Второй режим больше соответствует облику стандартных приложений Windows. В оконном режиме приходится работать с привычным окном приложения, расположенным на рабочем столе, как показано на рис. 12-4.

Оконный режим связан с определенными сложностями, поскольку поверхность вторичного буфера используется совместно с GDI. При непосредственной записи в видеопамять необходимо соблюдать осторожность и не выйти за пределы области вашего окна. Чтобы помочь вам в этом, DirectDraw предоставляет объект-ограничитель DirectDrawClipper, который присоединяется к первичному буферу. Ограничитель следит за окном, созданным на рабочем столе, и определяет границы памяти первичного буфера. В оконном режиме вы уже не можете переключать страницы, как это делается в полноэкранном режиме, и размер вторичного буфера обычно равен лишь размеру окна. Некоторые видеоадаптеры выполняют сложные перекрытия, которые позволяют в оконном режиме переключать страницы так, как это делается в полноэкранном; при соответствующей аппаратной поддержке DirectDraw позволяет вызвать функцию переключения Flip для поверхности в оконном режиме.

155.jpg

Рис. 12-4. Оконный режим

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

Поверхности и GDI


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

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

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

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

Интересный побочный эффект от использования видеопамяти в GDI прослеживается при попытке отладить программу, непосредственно работающую с пикселями, в графическом отладчике (например, отладчике Microsoft Visual C++). Перед тем, как работать с поверхностью, ее необходимо заблокировать. При этом DirectDraw блокирует подсистему Win 16, через которую осуществляется доступ к таким компонентам Windows, как USER и GDI. Когда DirectDraw заблокирует Winl6, GDI перестанет работать, поэтому ваш отладчик не сможет ничего вывести на экран и «повиснет».

Выход заключается в том, чтобы работать с отладчиком, не использующим GDI (например, WDEB386), или запустить Visual C++ в сеансе удаленной отладки, подключаясь к целевому компьютеру через кабель или сетевое соединение TCP/IP. Инструкции по удаленной отладке приведены в справочном разделе Books Online среды Visual C++.

Работа с DirectDraw


После краткого знакомства с «машинным отделением» DirectDraw, давайте посмотрим, как же пользоваться интерфейсом DirectDraw в наших приложениях. Код, которым мы будем заниматься, взят из примера, находящегося в каталоге DDeval. Это приложение написано мной специально для экспериментов с DirectDraw. Его даже трудно назвать приложением — скорее, это инструмент для тестирования и оценки, поэтому средства его пользовательского интерфейса ограничены. DDEval не делает ничего сверхъестественного, но показывает вам, как использовать GDI для работы с поверхностью, как запустить программу в полноэкранном или оконном режиме, как напрямую работать с пикселями поверхности. Тесты позволяют измерить скорость работы приложения, выраженную в количестве кадров в секунду (fps), чтобы вы могли наглядно представить себе производительность.

ПРИМЕЧАНИЕ

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

Структура программы DDEval


Я создал программу DDEval с помощью Visual C++ AppWizard. Я решил оформить ее в виде окна диалога, а не в виде приложения с интерфейсом SDI или MDI. Окно диалога изображено на рис. 12-5.

DDEval проводит четыре теста, каждый из которых может выполняться либо в оконном, либо в полноэкранном режиме. В оконном режиме размер окна выбирается из списка (от 320х200 до 1024х768). Количество цветов всегда совпадает с

156.jpg

Рис. 12-5. Приложение DDEval

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

При выборе полноэкранного режима программа составляет перечень всех возможных режимов. На моем компьютере Dell он состоит из 17 режимов, так что на однообразие жаловаться не приходится. Полноэкранные режимы не ограничиваются текущим количеством цветов. Даже если вы работаете с 8-битными цветами, то при желании можете провести тестирование в полноэкранном режиме с 16--битными цветами.

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

Программа DDEval


Начнем с простейшего: подготовки объекта CDirectDraw к работе и составления перечня доступных полноэкранных режимов. Управляющий объект CDirectDraw создается следующим образом:

void CDDEvalDIg::SetInitialState () (

// Создать объект DirectDraw m_pDD = new CDirectDraw;

BOOL b = m_pDD-»Create () ;

}

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

После того как создан объект CDirectDraw, можно составлять перечень доступных полноэкранных режимов. Приведенная ниже функция заполняет список в окне диалога DirectDraw Evaluation:

void CDDEvalDIg::ShowModes(

int iModes = m_pDD-»GetNumModes () ;

DDSORFACEDESC dds;

for (int i = 0; i « iModes; i++) ( m_pDD-»GetMode!nfo (i, &dds);

sprintf(buf,

"%41u x %41u x %21u", dds.dwWidth, dds.dwHeight,

dds.ddpfPixelFormat.dwRGBBitCount) m_cbModes.AddString(buf) ;

}

Как видно из листинга, мы определяем режимы и получаем описание поверхности для каждого из них. Затем для каждого режима конструируется строка, которая заносится в список. Код объекта CDirectDraw выглядит несколько сложнее, поскольку для нумерации режимов в DirectDraw используются функции косвенного вызова. Я реализовал функции CDirectDraw::GetNumModes и CDirectDraw::GetModelnfo с помощью одной функции косвенного вызова, расположенной в файле 3dDirDrw.cpp:

// Информационная структура для нумерации режимов typedef struct _EnumModeInfo {

int iModeCount;

int iMode;

DDSURFACEDESC ddSurf;

} EnumModeInfo;

// Функция косвенного вызова для нумерации // режимов устройства

static HRESULT FAR PASCAL EnumModesFn(LPDDSURFACEDESC psd, LPVOID pArg)

1

EnumModeInfo* pinfo = (EnumModeInfo*) pArg;

ASSERT(pinfo) ;

// Проверить правильность режима if (p!nfo-»iMode == p!nfo-»iModeCount) {

p!nfo-»ddSurf = *psd;

return DDENuMRET_CANCEL; // Прекратить нумерацию

}

p!nfo-»iModeCount++;

return DDENUMRET_OK;

}

// Определить количество поддерживаемых экранных режимов int CDirectDraw::GetNumModes()

{

ASSERT(m_pIDD) ;

EnumModeInfo mi;

mi.iModeCount = 0;

mi.iMode = -1;

m_hr = m_pIDD-»EnumDisplayModes (0, NULL, Smi, EnumModesFn) ;

ASSERT(SUCCEEDED(m_hr)) ;

return mi.iModeCount;

}

// Получить данные для заданного режима BOOL CDirectDraw::GetModeInfo(int iMode,

DDSURFACEDESC* pDesc)

{

int iModes = GetNumModes();

if (iMode »= iModes) return FALSE;

ASSERT(m_pIDD) ;

EnumModeInfo mi;

mi. iModeCount =0;

mi.iMode = iMode;

m hr = m pIDD-»EnumDisplayModes (0, NULL, &mi, EnumModesFn);

ASSERT(SUCCEEDED(m_hr)) ;

*pDesc = mi.ddSurf;

return TRUE;

}

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

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

Подготовка тестового окна


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

BOOL CTestWnd::Create(TESTINFO* pti)

{

// Сохранить информацию о тесте m_pTI = pti;

ASSERT(m_pTI) ;

// Создать объект DirectDraw m_pDD = new CDirectDraw;

BOOL b = m_pDD-»Create () ;

ASSERT(b);~

II Зарегистрировать класс окна

CString strClass =

AfxRegisterWndClass(CS_HREDRAW I CS_VREDRAW, ::LoadCursor(NULL, IDC_ARROW), (HBRUSH)::GetStockObject(GRAY_BRUSH)

// Задать стиль и размеры окна DWORD dwStyle = WS_VISIBLE ¦ WS_POPUP;

RECT re;

if (m_pTI-»bFullScreen) {

re.top = 0;

re.left = 0;

re.right = ::GetSystemMetrics(SM_CXSCREEN);

re.bottom = ::GetSystemMetrics(SM_CYSCREEN);

} else { // Оконный режим

dwStyle ¦= WS_CAPTION ¦ WS_SYSMENU;

re.top = 50;

re.left = 50;

re.bottom = re.top + m_pT I-»i Height;

re. right = re. left + m_pTI-»iWidth;

::AdjustWindowRect(&rc, dwStyle, FALSE);

if (!CreateEx(0,

strClass,

"DirectDraw Window",

dwStyle,

re.left, re.top,

re.right — re.left, re.bottom — re.top,

m pTI-»pParent-»GetSafeHwnd() ,

NULL)) {

return FALSE;

i

// Обеспечить отображение окна на экране UpdateWindow() ;

// Установить режим для окна

ASSERT(m_pTI) ;

if (m_pTI-»bFullScreen) f

b = m_pDD-»SetFull3creenMode (GetSafeHwndO ,

m_pTI-»iWidth,

m_pTI-»iHeight,

m_pTI-»iBpp) ;

} else (

b = m_pDD-»SetWindowedMode (GetSafeHwnd () ,

m_pTI-»iWidth,

m_pTI-»iHeight) ;

} ASSERT(b) ;

// Выполнить тестирование SetTimerfl, 100, NULL);

return TRUE;

»

В первой половине функции мы создаем объект CDirectDraw и тестовое окно, которое появляется на экране. Большей частью она состоит из стандартного кода Windows. После того как окно создано, объект CDirectDraw переводится в оконный или полноэкранный режим. Наиболее сложной оказывается завершающая часть. Функции SetFullScreenMode и SetWindowedMode равносильны вызову функции CDirectDraw::_SetMode, выполняющей всю работу по созданию первичного и вторичного буферов, а также связанной с ними палитры. Установка режима состоит из трех этапов:

1. Задание уровня кооперации (cooperative level), который определяет, какие действия разрешается выполнять с DirectDraw. Если приложение должно работать в окне, выбирается нормальный режим. Для полного экрана следует затребовать монопольный (exclusive) режим. Уровень кооперации помогает распределять ресурсы между системой и приложениями DirectDraw.

2. Создание первичного и вторичного буферов. Буфера являются поверхностями DirectDraw. Если мы собираемся работать в полноэкранном режиме, то создаем так называемую сложную переключаемую поверхность, состоящую из двух одинаковых буферов. Для работы в окне создается два отдельных буфера: первичный, используемый совместно с GDI, и вторичный, принадлежащий только приложению.

3. Определить, нужен ли ограничитель, и если да, то создать его.

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

BOOL CDirectDraw::_SetMode(HWND hWnd, int ex, int cy,

int bpp, BOOL bFullScreen) t

ASSERT(m_pIDD) ;

// Освободить все существующие буфера ReleaseAllO ;

// Задать уровень кооперации if (bFullScreen) {

if (!SetCooperativeLevel(hWnd, DDSCL_EXCLOSIVE ¦

DDSCL_FULLSCREEN)) ( return FALSE;

}

m_hr = m_pIDD-»SetDisplayMode (ex, cy, bpp) ;

if (FAILED(m_hr)) { return FALSE;

} m_bRestore = TRUE;

} else {

if (!SetCooperativeLevel(hWnd, DDSCL_NORMAL)) ( return FALSE;

} )

// Создать первичную и вторичную поверхности

m_iWidth = ex;

m_iHeight = су;

DDSURFACEDESC sd;

inemset(&sd, 0, sizeof(sd));

sd.dwSize == sizeof(sd);

if (bFullScreen) {

// Создать сложную переключаемую поверхность // с первичным и вторичным буферами sd.dwFlags = DDSD_CAPS

I DDSD_BACKBUFFERCOUNT;

sd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE

I DDSCAPS_FLIP

I DDSCAPS_COMPLEX

I DDSCAPS_3DDEVICE;

sd.dwBackBufferCount = 1;

// Создать поверхности для первичного и вторичного буфера

m_pFront Buffer = new CDDSurface/if ( !m_pFrontBuffer-»Create (this, Ssd) ) { return FALSE;

} ASSERT(m_pFrontBuffer) ;

// Получить указатель на присоединенный вторичный буфер DDSCAPS caps;

memset(Scaps, 0, sizeof(caps));

caps.dwCaps = DDSCAPS_BACKBUFFER;

m_pBackBuffer = m_pFrontBuffer-»GetAttachedSurface(&caps) ;

if (!m_pBackBuffer) {

delete m_pFrontBuffer;

m_pFrontBuffer = NULL;

return FALSE;

}

) else { // Оконный режим

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

// Создать поверхность первичного буфера. // Примечание: поскольку первичный буфер является // основной (существующей) поверхностью, // мы не указываем его ширину и высоту. sd.dwFlags = DDSD_CAPS;

sd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

m_pFrontBuffer = new CDDSurface;

if ( !m_pFrontBuffer-»Create (this, &sd) ) { return FALSE;

}

// Создать поверхность вторичного буфера sd.dwFlags = DDSD_WIDTH

I DDSD_HEIGHT

I DDSD_CAPS;

sd.dwWidth = ex;

sd.dwHeight = cy;

sd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN

I DDSCAPS_3DDEVICE;

m_pBackBuffer = new CDDSurface;

if ( !m_pBackBuffer-»Create(this, &sd)) (

delete m_pFrontBuffer;

m_pFrontBuffer = NULL;

return FALSE;

}

// Создать объект-ограничитель для первичного буфера, // чтобы вывод ограничивался пределами окна m_pClipper = new CDDClipper;

if ( !m_pClipper-»Create(this, hWnd) ) { return FALSE;

} }

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

Приведенный фрагмент содержит большое количество аспектов, которые я не стану подробно объяснять. Интерфейсы DirectDraw описаны в документации по DirectX SDK. Вы можете самостоятельно провести ряд экспериментов, взяв за основу данный код.

Тестирование


Для организации тестовых циклов используется таймер. Тестирующая функция вызывается из обработчика сообщений таймера:

void CTestWnd::OnTimer(UINT nIDEvent) {

// Выполнить следующий тест switch (m_pTI-»iTest) { case 1:

TestGDIText() ;

breaks-case 2:

TestGDIGfxf) ;

break;

case 3:

TestDDSprite() ;

break;

case 4:

TestDirectPixels() ;

break;

default:

ASSERT(0) ;

break;

}

Тестирование GDI при работе с текстом


В этом тесте я решил прибегнуть к услугам GDI для того, чтобы вывести в окне небольшой текст. При этом мне хотелось оценить, насколько медленно GDI будет справляться с данной задачей и будет ли вывод текста влиять на другие тесты. Результаты, полученные на моем компьютере Dell, меня вполне устроили — при выполнении теста в окне 320х200 скорость превышала 200 кадров в секунду. Все остальные тесты построены на основе этого кода, поэтому мы подробно рассмотрим его, шаг за шагом:

void CTestWnd::TestGDIText() (

ASSERT(m_pTI) ;

// Получить указатели на первичный и вторичный буфера

CDDSurface* pBB = m_pDD-»GetBackBuffer () ;

ASSERT(pBB) ;

CDDSurface* pFB = m_pDD-»GetFrontBuffer();

ASSERT(pFB) ;

// Получить прямоугольник, описывающий первичный буфер RECT rcFront;

if (m_pTI-»bFullScreen) {

pFB-»GetRect (rcFront) ;

} else (

GetClientRect(SrcFront) ;

ClientToScreen(&rcFront) ;

}

RECT rcBack;

pBB-»GetRect (rcBack) ;

DWORD dwStart = timeGetTime() ;

int nFrames = 100;

for (int iFrame = 0; iFrame « nFrames; iFrame++) (

DWORD dwNow = timeGetTime();

double fps;

if (dwNow == dwStart) (

fps = 0;

} else {

fps = iFrame * 1000.0 / (double)(dwNow — dwStart);

}

// Подготовить выводимый текст char buf[64];

sprintffbuf, "Frame td (%3.1f fps)", iFrame, fps);

// Получить DC для вторичного буфера CDC* pdc = pBB-»GetDC() ;

ASSERT(pdc) ;

// Заполнить буфер белым цветом pdc-»PatBlt (rcBack.left,

rcBack.top,

rcBack.right — rcBack.left,

rcBack.bottom — rcBack.top,

WHITENESS) ;

// Вывести текст pdc-»DrawText (buf,

-1,

srcBack,

DT_CENTER ¦ DT_BOTTOM I DT_SINGLELINE) ;

// Освободить DC pBB-»ReleaseDC(pdc) ;

// Переключить буфера

if (m_pTI-»bFullScreen) { pFB-»Flip() ;

} else (

pFB-»Blt (SrcFront, pBB, SrcBack) ;

} } )

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

Каждый цикл тестирования в функции TestGDIText состоит из следующих этапов:

1. Определить текущее время и вычислить текущую скорость вывода.

2. Создать выводимый текст.

3. Получить контекст устройства для вторичного буфера.

4. Стереть весь вторичный буфер, заполняя его белым цветом с помощью функции GDI.

5. Вызвать другую функцию GDI для вывода текста во вторичный буфер.

6. Освободить контекст устройства.

7. Переключить буфера и вывести результат на экран.

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

Разумеется, суть последнего этапа не всегда заключается в том, что первичный буфер меняется местами со вторичным. При работе в полноэкранном режиме функция CDDSurface::Flip действительно меняет буфера местами, однако в оконном режиме содержимое вторичного буфера копируется в первичный функцией Bit. В случае смены буферов не нужно беспокоиться о новой роли каждого из них, так как за всем следит DirectDraw, и когда мы требуем указатель на вторичный буфер, то всегда получаем именно то, что нужно.

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

Тестирование GDI при работе с графикой


Чтобы проверить производительность GDI при работе с графикой, я внес небольшие изменения в приведенную выше функцию и заставил ее рисовать средствами GDI прямоугольник, который перемещается внутри окна. Для этого мне пришлось добавить в функцию CTestWnd::TestGDIGfx приведенную ниже строку и в каждом цикле отслеживать положение прямоугольника в окне:

// Нарисовать прямоугольник pdc-»Rectangle (х, у, х+сх, у+су);

Вы можете самостоятельно протестировать интерфейс GDI и посмотреть, как он справляется с рисованием прямоугольников.

Тестирование DirectDraw при работе со спрайтами


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

void CTestWnd::TestDDSprite() {

DDSURFACEDESC sd;

memset(&sd, 0, sizeof(sd));

sd.dwSize = sizeof(sd);

sd.dwFlags = DDSD_WIDTH

I DDSD_HEIGHT

I DDSD_CAPS;

sd.dwWidth = ex;

sd.dwHeight = cy;

sd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

CDDSurface sprite;

BOOL b = sprite.Create(m_pDD, &sd) ;

ASSERT(b) ;

// Получить DC для поверхности спрайта.

// Нарисовать спрайт в виде красного круга на черном

фоне

CDC* pdc = sprite.GetDC();

ASSERT(pdc) ;

pdc-»PatBlt(0, 0, ex, cy, BLACKNESS);

CBrush br;

br.CreateSolidBrush(RGB(255, 0, 0) ) ;

CBrush* pbrOld = pdc-»SelectObject (Sbr) ;

pdc-»Ellipse (0, 0, ex, cy) ;

pdc-»SelectObject (pbrOld) ;

sprite.ReleaseDC(pdc) ;

// Задать черньй цвет в качестве цветового ключа

DDCOLORKEY ck;

ck.dwColorSpaceLowValue =0; // Черный ck.dwColorSpaceHighValue = 0;

sprite.SetColorKey(DDCKEY_SRCBLT, &ck) ;

Я создал поверхность, размер которой равен размеру спрайта (сх х су) и затем воспользовался функциями GDI, чтобы заполнить поверхность черным цветом и нарисовать красный круг. Поскольку цветовым-ключом поверхности задан черный цвет, при выводе спрайта рисуется только красный круг. Признаюсь, здесь я немного смошенничал и выбрал черный цвет в качестве цветового ключа лишь потому, что 0 соответствует черному цвету независимо от того, как его рассматривать — как RGB-значение или индекс палитры.

Создав простой спрайт, я добавил в функцию следующий фрагмент, который рисует спрайт во вторичном буфере:

// Закрасить буфер белым цветом CDC* pdc = pBB-»GetDC() ;

ASSERT(pdc) ;

pdc-»PatBlt (rcBack. left,

rcBack.top,

rcBack.right — rcBack.left,

rcBack.bottom — rcBack.top, i

WHITENESS) ;

// Вывести текст pdc-»DrawText (buf,

-1,

SrcBack,

DT_CENTER ¦ DT_BOTTOM ¦ DTJ3INGLELINE) ;

pBB-»Relea5eDC (pdc) ;

// Вывести спрайт RECT rcDst;

rcDst.left = x;

rcDst.top = y;

rcDst.right = rcDst.left + ex;

rcDst.bottom = rcDst.top + cy;

RECT rcSrc;

rcSrc.left = 0;

rcSrc.top = 0;

rcSrc.right = rcSrc.left + ex;

rcSrc.bottom = rcSrc.top + cy;

// Вызвать Bit с цветовым ключом pBB-»Blt (SrcDst, Ssprite, SrcSrc, DDBLT WAITIDDBLT KEYSRC) ;

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

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

Тестирование прямого доступа к пикселям


Я едва не отказался от этого теста. Когда в течение целого дня мой компьютер «зависал» через каждые пять минут, я возненавидел DirectDraw и решил, что в дальнейшем буду работать с графикой только через GDI. Все это время я пытался заблокировать буфер, чтобы получить возможность писать в него. Вместе с буфером почему-то блокировался и компьютер, и мне приходилось перегружаться. Намучившись, я лег спать, и в лучших нердовских традициях решение пришло во сне. Чтобы моя программа заработала, из нее нужно было убрать всего один символ. Привожу старую и новую версию функции CDDSurface::Unlock из файла SdDirDraw.cpp, которая причинила мне столько огорчений:

void CDDSurface::Unlock() (

if (!m_SurfDesc.IpSurface) return; // Поверхность

// не заблокирована

m_hr = m_pISurf-»Unlock(&m_Surf Desc. IpSurface) ;

ASSERT(SUCCEEDED(m_hr)) ;

m_SurfDesc.IpSurface = NULL;

}

void CDDSurface::Unlock () (

if (!m_SurfDesc.IpSurface) return; // Поверхность

// не заблокирована

m_hr != m_pISurf-»Unlock(m SurfDesc.IpSurface);

ASSERT(SUCCEEDED(m_hr)) ;

m_SurfDesc.IpSurface == NULL;

}

Удалось ли вам найти отличие? Эти указатели так похожи друг на друга — до тех пор, пока вы не попытаетесь их использовать!

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

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

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

Приведенный ниже фрагмент определяет формат пикселей для вторичного буфера:

void CTestWnd::TestDirectPixels() (

// Получить информацию о вторичном буфере

int iBpp = pBB-»GetBitsPerPixel () ;

ASSERT(iBpp »= 8);

int iWidth - pBB-»GetWidth() ;

int iHeight = pBB-»GetHeight () ;

int iPitch = pBB-»GetPitch() ;

// Получить RGB-маски DWORD dwRMask, dwGMask, dwBMask;

pBB-»GetRGBMasks (dwRMask, dwGMask, dwBMask) ;

// Для каждой маски определить ширину в битах // и количество бит, на которые маска // смещена от конца младшего байта DWORD dwRShift, dwGShift, dwBShift;

DWORD dwRBits, dwGBits dwBBits;

dwRShift = dwRBits = 0 dwGShift = dwGBits = 0 dwBShift = dwBBits = 0 if (iBpp » 8) {

DWORD d = dwRMask;

while ((d & 0х1) == 0) ( d = d »» 1;

dwRShift++;

)

while (d & 0х01) { d = d »» 1;

dwRBits++;

}

d = dwGMask;

while ( (d & 0х1) ==0) {

d = d »» 1;

dwGShift++;

}

while (d & 0х01) { d = d »» 1;

dwGBits++;

} d = dwBMask;

while ((d & 0х1) == 0) (

d = d »» 1;

dwBShift++;

}

while (d & 0х01) { d = d »» 1;

dwBBits++;

}

i

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

// Если буфер имеет 8-битную кодировку пикселей, // получить элементы палитры и присвоить им // нужные цветовые значения PALETTEENTRY ре [256];

BYTE r, g, b;

CDDPalette* pPal = pBB-»GetPalette () ;

if (pPal) {

pPal-»GetEntries (0, 256, ре) ;

// Задать нужные цветовые значения. // Мы воспользуемся моделью с 2-битной кодировкой // R, Си В-составляющих for (r = 0; r « 4; г++) { for (g = 0; g « 4; g++) ( for (b = 0; b « 4; b++) {

int index =10+r*16+g*4+b;

pe[index].peRed = r * 85;

ре[index].peGreen = g * 85;

pe[index].peBlue = b * 85;

}

// Заполнить оставшиеся элементы палитры серым цветом, // чтобы их можно было увидеть при отладке for (int i = 10 + 4*4*4; i « 246; i++) {

ре[i].peRed = 192;

pe[i].peGreen = 192;

pe[i].peBlue = 192;

}

// Обновить палитру pPal-»SetEntries (0, 256, ре) ;

// Удалить объект палитры delete pPal;

}

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

// Заблокировать буфер и получить указатель.

// ВНИМАНИЕ: Не пытайтесь включать пошаговое выполнение

// до вызова Unlock.

BYTE* pBuf = (BYTE*) pBB-»Lock () ;

if (pBuf) (

for (int у = 0; у « iHeight; y++) (

// Определить смещение начала строки int n = iwidth;

DWORD dwOffset = у * iPitch; // В байтах

// Получить цвет int ir = GetRValue(cIrLine);

int ig = GetGValue(clrLine);

int ib = GetBValue(cIrLine);

// Вывести пиксели непосредственно в буфер

switch (iBpp) (

case 8: {

// Найти индекс для цвета

// в соответствии с принятой нами моделью int index = 10 + (ir / 85) * 16 + (ig /85) * 4 + (ib / 85) ;

BYTE* p = pBuf + dwOffset;

while (n-) {

*p++ = (BYTE) index;

) breaks-case 16: (

// Построить цветовое значение

DWORD dw = (ir »» (8 - dwRBits)) ««

dwRShift

I (ig »» (8 - dwGBits)) «« dwGShift

I (ib »» (8 - dwBBits)) «« dwBShift;

WORD w = (WORD)dw;

WORD* p = (WORD*)(pBuf + dwOffset);

while (n—) *p++ = w;

) breaks-case 24:

// Упражнение для самостоятельной работы:

breaks-case 32: {

DWORD dw = (ir »» (8 - dwRBits)) «« dwRShift

I (ig »» (8 - dwGBits)) «« dwGShift

I (ib »» (8 - dwBBits)) «« dwBShift;

DWORD* p = (DWORD*)(pBuf + dwOffset);

while (n—) *p++ = dw;

) breaks-default:

break;

i

// Перейти к следующему цвету NextColor(clrLine) ;

} pBB-»Unlock() ;

// Снова можно работать с отладчиком

> NextColor(cIrStart) ;

Программа несколько отличается для разных цветовых кодировок, поскольку пиксели занимают разное количество байт и для них требуются различные RGB-маски. Пожалуйста, соблюдайте осторожность при выполнении арифметических операций с указателями. Вы можете перепутать указатели на байт с указателями на DWORD и получить неверный результат, поскольку компилятор прибавит 2 или 4 вместо 1 или наоборот.

Чтобы тест генерировал другой набор линий, следует внести изменения в функцию NextColor, которая определяет цвет для вывода следующей линии.

Наверняка вы обратили внимание, что я пропустил код для 24-битной кодировки. Мой видеоадаптер работает только с 8-, 16-и 32-битными цветами, поэтому я не мог протестировать 24-битный код. Результаты данного теста приведены на цветной вкладке.

Веселье продолжается


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

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

[Назад][Содержание][Вперед]



  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2024
    Реклама на сайте