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


Какие же задачи призван решать DirectX API? Прежде всего — это создание производительных приложений, в которых используются 3Dграфика, звук, а также сетевые соединения между компьютерами. Справедливости ради замечу, что такого рода приложения можно создавать совершенно самостоятельно, добиваясь как можно более полного соответствия кода вашим нуждам. (Но, к сожалению, это отнимет чуть ли не в десять раз больше времени.) Следует также учесть и то, что за четыре года существования (с 1995) DirectX API получил поддержку производителей аппаратного обеспечения. Это значит, что многие из современных компонентов компьютера предусматривают аппаратную поддержку функций DirectX. Если вы решили создать графическое приложение, перед вами три пути.

 

Можно программировать для DOS (примен 32-битный DOS-extender), использовать Windows GDI, и, наконец, DirectX. (Программирование для X-Windows станет возможным года через два, когда Linux отвоюет у Windows место под солнцем.) Способ создания приложения следует выбирать исходя из требований к его производительности и объему.

 

Что же может “предложить” программисту DirectX?

 

API DirectX — это довольно сложная система, в которой (условно) можно выделить пять компонентов.

  • DirectDraw — функции быстрого доступа к видеопамяти и аппаратной акселерации видеокарт.
  • Direct3D — API дл создания приложений, в которых используется трехмерная графика.
  • DirectPlay — высокоуровневые функции дл взаимодействия по модему или через сеть.
  • DirectInput — функции для работы с джойстиком, клавиатурой, мышкой.
  • DirectSound — функции для вывода звука (в том числе с применением аппаратной акселерации). 

Надо заметить, что разработчиками из Microsoft предоставлены все возможности дл эффективного программировани мультимедийных приложений в среде Windows 95/98 — этого у “окошек” не отнять. Если эту операционную систему и ругают на каждом компьютерном углу, то уж наверняка не за плохие “игрушки”! В самом деле, схожих “по весу” систем дл программирования графики и звука без использования специализированных рабочих станций нет. (Не забудем, однако, об OpenGL — API для создания трехмерной графики, разработанном в Silicon Graphics и ставшем уже промышленным стандартом. Хотя в Silicon Graphics не предусмотрели, например, унифицированную систему для вывода звука!) Главные компоненты DirectX — DirectDraw и Direct3D. Мы рассмотрим основы программирования с использованием интерфейса DirectDraw — ввиду его сравнительной простоты. После этого читатель сможет сам разобраться в общих принципах программирования в DirectX. Прежде всего — о среде программирования. Я использую (и всем настоятельно рекомендую) Watcom C/C++ 11.0. Почему именно Watcom? “Сочетание интегрированной среды разработки, набора инструментов и стоящей на грани искусства системы компиляции делает Watcom C/C++ уникальной по возможностям и продуктивности” — написано на коробке с дистрибутивом.

 

Совершенно с этим согласен — Watcom предоставляются просто безграничные возможности для оптимизации кода. С другой стороны, если вы предпочитаете красивый интерфейс интегрированной среды, пользуйтесь Borland C++ (Builder) или Visual C++ (у Watcom такого интерфейса, можно сказать, нет). Но, так или иначе, все исходные коды этой статьи без проблем скомпилируются почти любым компилятором C++ — лишь бы были файлызаголовки ddraw.h, d3d.h, d3dcaps.h, d3drm.h, d3drmdef.h, d3drmobj.h, d3drmwin.h, d3dtypes.h, dplay.h, dsound.h и библиотеки DirectX ddraw.lib, d3drm.lib, dplay.lib, dsound.lib (Библиотеки Watcom и Microsoft несовместимы).

 

Теоретические сведения

 

В DirectX SDK (Software Development Kit — набор дл разработки программного обеспечения) используется COM-технология. Эта аббревиатура означает Component Object Model — “компонентная объектная модель”. Речь идет о чемто среднем между объектным и структурным подходами к программированию. Так, вызов функций-членов данного объекта производится точно так же, как и в ООП; объект при этом не может иметь ассоциированных с ним статических данных. У объекта нет привычных для нас деструктора и конструктора. Любое взаимодействие с данным объектом происходит через интерфейсы. Интерфейсом можно назвать набор функций, объединенных общим названием (и служащих одной цели), которые можно “привязать” (в принципе) к любому объекту. Например, для связи с объектом DirectDraw служит интерфейс (или, проще, набор функций) IDirectDraw. Рассмотрим функцию интерфейса IDirectDraw::CreateSurface (о том, для чего она предназначена, — далее). Чтобы вызвать ее из экземпляра объекта DirectDraw, используется такой код:

 

LPDIRECTDRAW lpDIRDRAW;
...
lpDIRDRAW->CreateSurface( ... );

 

Пока, кажется, все понятно. Все интерфейсы основаны (являютс производными — derived) на интерфейсе IUnknown, у которого всего три функции. Самая интересная из них — QueryInterface: вызвав ее, программист может определить, поддерживает ли тот или иной объект какой-либо интерфейс. Синтаксис функции:

 

HRESULT QueryInterface(REFIID riid, LPVOID* obp);

 

Первым параметром задаетс идентификатор данного интерфейса. Идентификаторы интерфейсов, относящихся к DirectX, можно найти в файлах-заголовках d3d.h, d3drm.h, d3drmobj.h, d3drmwin.h, ddraw.h, dplay.h, dsound.h (список всех интерфейсов Windows занесен в ключ Mycomputer\HKEY_CLASSES_ROOT\ Interface системного реестра). Через второй параметр функци возвращает указатель на необходимый интерфейс, если найдет его.
 

Рассмотрим листинг 1. Он содержит основную процедуру программы, использующей средства DirectDraw.

 

Фрагмент простейшей программы с использованием DirectDraw 

 

Листинг 1
 
int doInit( HINSTANCE hInst, int show)
{
HWND hwnd;
WNDCLASS wc;
DDSURFACEDESC ddsd;
HRESULT ddrval;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon( NULL, IDI_WINLOGO);
wc.hCursor = LoadCursor( NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = className;
RegisterClass( &wc);
hwnd = CreateWindowEx( WS_EX_TOPMOST, className,
wndName, WS_POPUP, 0, 0,
GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),NULL,NULL,hInst,NULL);
if( !hwnd) return FALSE;
ShowWindow( hwnd, show);
UpdateWindow( hwnd);
ddrval = DirectDrawCreate( NULL, &lpDD, NULL);
if( ddrval != DD_OK ) return FALSE;
ddrval = lpDD->SetCooperativeLevel( hwnd,
DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
if( ddrval != DD_OK ) return FALSE;
ddrval = lpDD->SetDisplayMode( 640, 480, 8);
if( ddrval != DD_OK ) return FALSE;
ddsd.dwSize = sizeof( ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
ddrval = lpDD->CreateSurface( &ddsd, &lpDDS,
NULL);
if( ddrval != DD_OK ) return FALSE;
lpDDS->Lock( NULL, &ddsd, DDLOCK_WAIT, NULL);
memset( ddsd.lpSurface, 0, 640*480);
lpDDS->Unlock( NULL );
return TRUE;
}
 

Полностью исходный код этой программы здесь. Мы будем исходить из того, что читатель знаком хотя бы с основами программирования с помощью Windows API — иначе статья была бы раз в десять больше. Рассмотрим только приведенную в листинге функцию doInit(HINSTANCE, int), которая вызываетс из WinMain.

 

Первая “незнакомая” команда выглядит так:

 

LPDIRECTDRAW lpDD;
...
HRESULT ddrval;
...
ddrval = DirectDrawCreate( NULL,
&lpDD, NULL);

 

Вот объявление этой функции в файле заголовка:

 

HRESULT DirectDrawCreate(GUID FAR *
lpGUID, LPDIRECTDRAW FAR * lplpDD,
IUknown FAR * pUnkOuter);

 

Функцией DirectDrawCreate создается новый объект DirectDraw. Первый и третий параметры функции для нас не важны (они почти всегда остаются NULL), а второй — это указатель на переменную типа LPDIRECTDRAW, в которую при успешном выполнении функции будет занесен указатель на объект. Программист может создать сколько угодно объектов DirectDraw.

 

Следуем дальше:

 

ddrval = lpDD->SetCooperativeLevel
(hwnd, DDSCL_EXCLUSIVE |
DDSCL_FULLSCREEN);

 

Синтаксис функции:

 

HRESULT SetCooperativeLevel(HWND
hWnd, DWORD dwFlags);

 

Этой функцией устанавливается режим взаимодействия объекта DirectDraw и системы GDI Windows. Первым параметром функции задаетс дескриптор окна, к которому привязываетс объект DirectDraw; значения второго параметра определяют режимы доступа к экрану: DDSCL_EXCLUSIVE — полный доступ; DDSCL_FULLSCREEN — полноэкранный режим, при котором отображается только окно приложения; DSCL_NORMAL — приложение в обычном окне Windows. Итак, если предполагается создать “полноэкранное” приложение, то указывайте два параметра — DDSCL_EXCLUSIVE и DDSCL_FULLSCREEN, а для “оконного” — только DDSCL_NORMAL. ddrval = lpDD->SetDisplayMode (640, 480, 8); Функцией SetDisplayMode разрешение и глубина цвета устанавливаются только при работе в полноэкранном режиме. Ее синтаксис:

 

HRESULT SetDisplayMode
(DWORD dwWidth, DWORD dwHeight,
DWORD dwBPP, DWORD dwRefreshRate,
DWORD dwFlags);

 

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

 

Что такое "плоскость"

Грубо говоря, “плоскость” — это обычный линейный буфер для хранени растровых изображений (bitmaps). Любое приложение DirectX должно иметь одну первичную плоскость (primary surface). Величина созданной первичной плоскости и глубина представлени цвета должны быть как у Windows GUI. С помощью этой плоскости можно выводить изображения прямо на экран компьютера. В свою очередь, количество вторичных плоскостей (off-screen surface, или “внеэкранна плоскость”) не ограничено.
 

Следующая по порядку функция:

 

LPDIRECTDRAWSURFACE lpDDS;
...
DDSURFACEDESC ddsd;
...
ddsd.dwSize = sizeof( ddsd);
ddsd.dwFlags = DDSD_CAPS ;
ddsd.ddsCaps.dwCaps =
DDSCAPS_PRIMARYSURFACE;
ddrval = lpDD->CreateSurface
(&ddsd, &lpDDS, NULL);

 

Как уже было сказано, здесь вызывается функция интерфейса IDirectDraw, создающая новый экземпляр объекта DirectDrawSurface c интерфейсом IDirectDrawSurface. Синтаксис:

 

HRESULT CreateSurface
(LPDDSURFACEDESC lpDDSurfaceDesc,
LPDIRECTDRAWSURFACE FAR *
lplpDDSurface, IUnknown FAR *
pUnkOuter);

 

Первый параметр представляет собой указатель на переменную типа DDSURFACEDESC, определяющую параметры создаваемого объекта-плоскости (Внимание! Много переменных в записи определения этого типа здесь опущено — оставлены только самые важные для нас).

 

typedef struct _DDSURFACEDESC
{
DWORD dwSize;
DWORD dwFlags;
DWORD dwHeight;
// при использовании в dwFlags
указывать DDSD_HEIGHT
DWORD dwWidth;// -*DDSD_WIDTH
...
DWORD dwBackBufferCount,
// -*DDSD_BACKBUFFERCOUNT
...
LPVOID lpSurface;
DDSCAPS ddsCaps;
//DDSD_CAPS
} DDSURFACEDESC,
FAR* LPDDSURFACEDESC;

 

Ясно, что инициализировать все переменные-члены DDSURFACEDESC (а их 18), как, например, инициализируется переменная типа WNDCLASS, перед регистрацией оконного класса нерационально. Поэтому в dwFlags указываютс все переменные, на которые функция будет “обращать внимание”. Если нужно задать ширину и высоту “плоскости”, пишем следующую строку:

 

ddsd.dwFlags = DDSD_WIDTH |
DDSD_HEIGHT;

    Теперь о переменной ddsCaps:

typedef struct _DDSCAPS
{
DWORD dwCaps;
} DDSCAPS, FAR* LPDDSCAPS;

 

Ее переменная-член dwCaps может принимать такие значения: DDSCAPS_PRIMARYSURFACE — тогда созданна плоскость будет первичной (primary). Наличие первичной плоскости обязательно, так как именно через нее графика будет выводитьс на экран. В нашем примере мы создаем только первичную плоскость. DDSCAPS_WRITEONLY — плоскость будет доступна только дл записи. DDSCAPS_COMPLEX и DDSCAPS_FLIP — будет создано несколько плоскостей, присоединенных к главной. Об этом мы поговорим далее, а пока лишь скажу, что эти параметры используются вместе с переменной dwBackBufferCount. Ее индикатор — DDSD_BACKBUFFERCOUNT, помещенный в dwFlags. DDSCAPS_BACKBUFFER — служит для создания так называемого backbuffer — “бэкбуфера” (буквальный перевод — “задний буфер” — хоть и более понятен, но не очень благозвучен). Бэкбуфер — это “плоскость” (не отображающаяся на экране), где хранятс текстуры или спрайты. Во втором параметре функция возвращает указатель на созданный объект DIRECTDRAWSURFACE. Третим параметром в большинстве случаев можно оставлять NULL. Нашей программой создается только первичная плоскость. Приведем пример создания внеэкранной плоскости:

 

ddsd.dwFlags = DDSD_CAPS |
DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps =
DDSCAPS_OFFSCREENPLAIN;
ddsd.dwHeight = 200;
ddsd.dwWidth = 200;
ddrval = lpDD->CreateSurface(
&ddsd, &lpDDSOffscreen, NULL );

 

Нельзя не упомянуть еще об одной очень удобной функциональной возможности — о создании “переключающихся” (flipping) плоскостей. В этом случае наряду с первичной плоскостью создается несколько вторичных, причем при последующем обращении к функции интерфейса IDirectDrawSurface

 

HRESULT Flip( LPDIRECTDRAWSUR-
FACE4 lpDDSurfaceTargetOverride,
DWORD dwFlags);

 

экранная плоскость “поменяетс местами” с внеэкранной. Если внеэкранных плоскостей несколько, происходит поочередное переключение.

 

Естественно, такая возможность незаменима при анимации. Вот пример создани переключающихся плоскостей:

 

ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS |
DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps =
DDSCAPS_PRIMARYSURFACE |
DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
ddrval = lpDD->CreateSurface(
&ddsd, &lpDDSPrimary, NULL );

 

Мы создаем первичную плоскость с одной присоединенной к ней внеэкранной. Доступ к внеэкранным плоскостям осуществляется через функцию интерфейса IDirectDrawSurface:

 

HRESULT GetAttachedSurface
( LPDDSCAPS2 lpDDSCaps,
LPDIRECTDRAWSURFACE FAR *
lplpDDAttachedSurface);

Вот пример использования этой функции:

ddscaps.dwCaps =
DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->
GetAttachedSurface(&ddscaps,
&lpDDSBack);

 

После выполнени функции в переменной lpDDSBack остаетс указатель на присоединенную плоскость. Теперь коснемся самого важного, на мой взгляд, вопроса – как именно происходит обращение к плоскостям? Рассмотрим три возможных способа:

 

    1. Использование Windows GUI

 

HDC hdc;
...
lpDDSPrimary->GetDC( &hdc);
...
/* получив контекст устройства в hdc, можем использовать средства Windows GUI */
...
lpDDSPrimary->ReleaseDC( hdc);

 

    2. Прямой доступ к плоскости

 

lpDDS->Lock( NULL, &ddsd,
DDLOCK_WAIT , NULL );
...
// теперь в ddsd.lpSurface имеем
// указатель на плоскость
lpDDS->Unlock( NULL );

 

Этот способ мы используем в нашей программе, однако там мы просто заполняем плоскость нулями:

 

memset(ddsd.lpSurface, 0, 640*480);

 

Итак, мы “замыкаем” нашу плоскость. Следует заметить, однако, что при “замыкании” все процессы в системе приостанавливаются до “размыкания” (кстати, GetDC тоже вызывает функцию Lock). Другими словами, эта часть программы всегда будет “бутылочным горлышком”. 3. Использование битового переноса блоков (BitBLiTting — Bit Block Transfer). В этом случае “работают” такие функции интерфейса IDirectDrawSurface:

 

HRESULT Blt( LPRECT lpDestRect,
LPDIRECTDRAWSURFACE4
lpDDSrcSurface, LPRECT lpSrcRect,
DWORD dwFlags, LPDDBLTFX
lpDDBltFx );
HRESULT BltBatch( LPDDBLTBATCH
lpDDBltBatch, DWORD dwCount,
DWORD dwFlags );
HRESULT BltFast( DWORD dwX, DWORD
dwY, LPDIRECTDRAWSURFACE4
lpDDSrcSurface, LPRECT lpSrcRect,
DWORD dwTrans );

 

Вот пример битового переноса:

 

RECT rcRect;
...
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = 640;
rcRect.bottom = 480;
ddrval = lpDDSPrimary->BltFast
(0, 0, lpDDSBack, &rcRect, FALSE);

 

Здесь производитс пересылка битового блока из плоскости lpDDSBack в lpDDSPrimary с использованием функции BltFast. Переменная rcRect указывает координаты пересылаемого битового блока, а два первых параметра — координаты, по которым этот блок будет переслан. После выполнени программы все объекты DirectX следует выгрузить из памяти. Для этого служит функция Release(void):

 

lpDDS->Release();

 

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

2004.11.04
19.03.2009
В IV квартале 2008 г. украинский рынок серверов по сравнению с аналогичным периодом прошлого года сократился в денежном выражении на 34% – до $30 млн (в ценах для конечных пользователей), а за весь календарный год – более чем на 5%, до 132 млн долл.


12.03.2009
4 марта в Киеве компания Telco провела конференцию "Инновационные телекоммуникации", посвященную новым эффективным телекоммуникационным технологиям для решения задач современного бизнеса.


05.03.2009
25 февраля в Киеве компания IBM, при информационной поддержке "1С" и Canonical, провела конференцию "Как сохранить деньги в условиях кризиса?"


26.02.2009
18-19 февраля в Киеве прошел юбилейный съезд ИТ-директоров Украины. Участниками данного мероприятия стали ИТ-директора, ИТ-менеджеры, поставщики ИТ-решений из Киева, Николаева, Днепропетровска, Чернигова и других городов Украины...


19.02.2009
10 февраля в Киеве состоялась пресс-конференция, посвященная итогам деятельности компании "DiaWest – Комп’ютерний світ" в 2008 году.


12.02.2009
С 5 февраля 2009 г. в Киеве начали работу учебные курсы по использованию услуг "электронного предприятия/ учреждения" на базе сети информационно-маркетинговых центров (ИМЦ).


04.02.2009
29 января 2009 года в редакции еженедельника "Computer World/Украина" состоялось награждение победителей акции "Оформи подписку – получи приз!".


29.01.2009
22 января в Киеве компания "МУК" и представительство компании Cisco в Украине провели семинар для партнеров "Обзор продуктов и решений Cisco Small Business"

 

 
 
Copyright © 1997-2008 ИД "Комиздат".