DirectDraw предназначен для непосредственной работы с видеопамятью. Для этого он по возможности использует аппаратно-зависимый доступ. Самое интересное для нас это то, что аппаратно-зависимый доступ выглядит как независимый. Это означает, что одна и та же функция будет использовать аппаратные средства разных видеокарт. Если видео карта не поддерживает каких-либо из возможностей DirectDraw, то они эмулируются. Функции DirectDraw находятся в библиотеке Ddraw.dll и исполнен в виде COM интерфейсов.
Я не буду глубоко вдаваться в подробности аппаратной работы (HAL) и эмуляции (HEL) с помощью DirectDraw , они тебе на фиг не нужны. Хотя все авторы книг обожают забивать место этой ерундой. Они наверно считают, что если ты будешь это знать, то твоя прога будет работать лучше. Чушь. Поэтому давай переходить к делу.
DirectDraw , грубо говоря, состоит из:
DirectDrawSurface - область видеопамяти. К ней можно обращаться напрямую и производить практически любые действия.
DirectDrawPalette - интерфейс для работы с палитрами. Он нужен, если ты настроился на 256 или не дай бог 16 цветную игру.
DirectDrawClipper - этот интерфейс отвечает за обрезания. Он нужен, если ты пишешь оконное приложение. В этом случае тебе понадобится обрезать всё, что выходит за пределы окна.
Я думаю, что хватит балаболить. Пора перейти к конкретному примеру. В качестве примера у нас будет бегающий по экрану красный квадратик. Для создания DirectX приложений, тебе понадобятся заголовочные файлы. Их ты можешь взять здесь. Я не буду приводить исходники проги, возьми их здесь, а я буду только объяснять, что происходит на каждом этапе.
Когда создаётся форма (процедура FormCreate), я инициализирую переменные FRect, FYAdd и FХAdd. Я их буду использовать во время рисования. После этого я устанавливаю процедуру, которая будет вызываться в свободное время Application.OnIdle := ProgIdle; . Если приложение ничего не делает, то управление будет передаваться процедуре ProgIdle .
Когда форма готова к употреблению я в событии FormShow вызываю процедуру StartDX , которая инициализирует DirectX.
Самая первая функция, на которую ты наткнёшься в процедуре StartDX будет DirectDrawCreate .
function DirectDrawCreate(
lpGUID: PGUID;
out lplpDD: IDirectDraw;
pUnkOuter: IUnknown):
HResult; stdcall;
Эта функция создаёт объект DirectDraw. Эта функция может вызываться несколько раз, но для нормальной работы достаточно и одного. Рассмотрим подробнее её параметры:
lpGUID - используемый для вывода драйвер. Возможные значения: DDCREATE_HARDWAREONLY - использовать только аппаратные возможности; DDCREATE_EMULATIONONLY - использовать только эмуляцию; nil - использовать аппаратные возможности и эмуляцию.
IDirectDraw - указатель на объект IDirectDraw, который будет инициализирован.
pUnkOuter - зарезервирован до лучших времён, обязательно должен быть nil.
Возвращаемые значения: DDERR_DIRECTDRAWALREADYCREATED, DDERR_GENERIC, DDERR_INVALIDDIRECTDRAWGUID, DDERR_INVALIDPARAMS, DDERR_NODIRECTDRAWHW, DDERR_OUTOFMEMORY. Если всё прошло удачно, то возвращает DD_OK.
После создания объекта DirectDraw необходимо получить доступ к DirectDraw2. Для этого мы вызываем метод QueryInterface у ADirectDraw. В качестве первого параметра передаётся GUID, который говорит, что нам нужен DirectDraw2. В качестве второго параметра передаётся указатель на DirectDraw2, который будет инициализирован.
Зачем нам нужен DirectDraw2? Это более новая версия DirectDraw, которая появилась в DirectX2 (если мне не изменяет память). DirectDraw сохранён для совместимости со старыми приложениями, поэтому мы будем прогрессивными и воспользуемся DirectDraw2.
После создания DirectDraw2, DirectDraw1 больше не нужен. Он использовался только для инициализации DirectDraw2, поэтому ссылку на него можно смело уничтожать ADirectDraw:=nil.
Затем, с помощью функции SetCooperativeLevel объекта DirectDraw2 мы устанавливаем флаги используемого режима работы.
function SetCooperativeLevel
(hWnd: HWND; - указатель на окно.
dwFlags: DWORD - флаги.
): HResult; stdcall;
С первым параметром всё ясно, я передал указатель на главную форму. Второй параметр, это флаги, которые могут иметь следующие значения:
DDSCL_ALLOWMODEX - Позволяет использование ModeX режимов дисплея.
DDSCL_ALLOWREBOOT - Позволяет реагировать на CTRL_ALT_DEL во время полноэкранного исключительного режима.
DDSCL_FULLSCREEN-Указывает, что исключительный владелец режима будет ответственен за всю основную поверхность. GDI может игнорироваться.
DDSCL_NORMAL-Указывает, что прикладная программа будет функционировать как обычная прикладная программа Windows.
DDSCL_NOWINDOWCHANGES -Указывает, что DirectDraw не позволяется минимизировать или восстановить окно прикладной программы при активации.
Внимание! Не все сочетания флагов ты можешь использовать. Например, ты не можешь установить флаг DDSCL_NORMAL вместе с DDSCL_EXCLUSIVE, потому что оконное приложение не может быть эксклюзивным.
Затем, с помощью функции SetDisplayMode объекта DirectDraw2 я устанавливаю размеры экрана и глубину цвета. Если ты будешь создавать оконное приложение, то эту функцию вызывать нельзя. Подумай сам, как можно отдельному окну задавать размеры всего экрана?
function SetDisplayMode(
dwWidth, //Ширина экрана
dwHeight, //Высота экрана
dwBpp: DWORD; //Число бит на пиксел
dwRefreshRate: DWORD;// Частота развёртки (0-по использовать умолчанию)
dwFlags: DWORD)// Зарезервирован, должен быть 0.
: HResult; stdcall;
После всего этого я создаю поверхности с помощью CreateSurface, всё того же объекта DirectDraw2.
function CreateSurface(
const lpDDSurfaceDesc: TDDSurfaceDesc;//Параметры поверхности
out lplpDDSurface: IDirectDrawSurface; //Указатель на создаваемую поверхность
pUnkOuter: Iunknown //Зарезервирован, должен быть nil
): HResult;
Структура TDDSurfaceDesc заслуживает отдельного разговора:
TDDSurfaceDesc = record
dwSize: DWORD;//Размер структуры
dwFlags: DWORD;//Флаги
dwHeight: DWORD; //Высота поверхности
dwWidth: DWORD; //Ширина поверхности
case Integer of
0: (
lPitch: Longint; Расстояние до начала следующей линии
dwBackBufferCount: DWORD;//Число дополнительных буферов
case Integer of
0: (
dwMipMapCount: DWORD; //Число уровней mipmap.
dwAlphaBitDepth: DWORD;//Глубина альфа-буфера
dwReserved: DWORD;Зарезервирован
lpSurface: Pointer;Указатель на память связанной поверхности
ddckCKDestOverlay: TDDColorKey;//Цветовой ключ для оверлея
ddckCKDestBlt: TDDColorKey;//Цветовой ключ для Blt.
ddckCKSrcOverlay: TDDColorKey;//Цветовой ключ для источника оверл.
ddckCKSrcBlt: TDDColorKey; // Цветовой ключ для источника Blt
ddpfPixelFormat: TDDPixelFormat;// Формат пиксела
ddsCaps: TDDSCaps;//Возможности поверхности
);
1: (
dwZBufferBitDepth: DWORD;// Глубина z-буфера
);
2: (
dwRefreshRate: DWORD;//Частота обновления
);
);
1: (
dwLinearSize: DWORD
);
end;
dwFlags может принимать значения:
DDSD_ALL - Все входные члены имеют силу.
DDSD_ALPHABITDEPTH - dwAlphaBitDepth имеет силу.
DDSD_BACKBUFFERCOUNT - dwBackBufferCount имеет силу.
DDSD_CAPS - ddsCaps имеет силу.
DDSD_CKDESTBLT - ddckCKDestBlt имеет силу.
DDSD_CKDESTOVERLAY - ddckCKDestOverlay имеет силу.
DDSD_CKSRCBLT - ddckCKSrcBlt имеет силу.
DDSD_CKSRCOVERLAY - ddckCKSrcOverlay имеет силу.
DDSD_HEIGHT - dwHeight имеет силу.
DDSD_LPSURFACE - lpSurface имеет силу.
DDSD_MIPMAPCOUNT - dwMipMapCount имеет силу.
DDSD_PITCH - lPitch имеет силу.
DDSD_PIXELFORMAT - ddpfPixelFormat имеет силу.
DDSD_REFRESHRATE - dwRefreshRate имеет силу.
DDSD_WIDTH - dwWidth имеет силу.
DDSD_ZBUFFERBITDEPTH - dwZBufferBitDepth имеет силу.
Остальные параметры ты можешь понять по комментариям. Единственное, что мне придётся расписать, так это структуру DDPIXELFORMAT:
TDDPixelFormat = record
dwSize: DWORD; //Размер структуры
dwFlags: DWORD;//Влаги
dwFourCC: DWORD;// FourCC код
case Integer of
0: (
dwRGBBitCount: DWORD;// RGB биты на пиксель
dwRBitMask: DWORD; // Маска для красных битов.
dwGBitMask: DWORD; //Маска для зелёных битов.
dwBBitMask: DWORD; //Маска для голубых :) битов.
dwRGBAlphaBitMask: //DWORD; Маска для alpha канала
);
1: (
_union1a: DWORD;
_union1b: DWORD;
_union1c: DWORD;
_union1d: DWORD;
dwRGBZBitMask: DWORD;
);
2: (
dwYUVBitCount: DWORD; //YUV биты на пиксель
dwYBitMask: DWORD; // Маска для Y битов
dwUBitMask: DWORD; // Маска для U битов
dwVBitMask: DWORD; Маска для V битов
dwYUVAlphaBitMask: DWORD; Маска для alpha канала
);
3: (
_union3a: DWORD;
_union3b: DWORD;
_union3c: DWORD;
_union3d: DWORD;
dwYUVZBitMask: DWORD;
);
4: (
dwZBufferBitDepth: DWORD; Глубина Z-буфера
dwStencilBitDepth: DWORD; Глубина ещё какого-то буфера
dwZBitMask: DWORD; // Маска для Z
dwStencilBitMask: DWORD; // Маска для Setnil буфера
);
5: (
dwAlphaBitDepth: DWORD; //Глубина Alpha канала
);
6: (
dwLuminanceBitCount: DWORD;
dwLuminanceBitMask: DWORD;
_union6a: DWORD;
_union6b: DWORD;
dwLuminanceAlphaBitMask: DWORD;
);
7: (
dwBumpBitCount: DWORD;
dwBumpDuBitMask: DWORD;
dwBumpDvBitMask: DWORD;
dwBumpLuminanceBitMask: DWORD;
);
end;
dwFlags может содержать следующие значения
DDPF_ALPHA - Формат пикселя описывает только alpha поверхность.
DDPF_ALPHAPIXELS - Поверхность имеет информацию об alpha в формате пикселя.
DDPF_COMPRESSED - Поверхность примет данные пикселя в определенном формате и сожмет их в течение операции записи.
DDPF_FOURCC - FourCC имеет силу.
DDPF_PALETTEINDEXED1 - цвет индексированн 1 битом.
DDPF_PALETTEINDEXED2 - цвет индексированн 2 битами.
DDPF_PALETTEINDEXED4 - цвет индексированн 4 битами.
DDPF_PALETTEINDEXED8 - цвет индексированн 8 битами.
DDPF_PALETTEINDEXEDTO8 - 1-, 2-, или с 4 битами цвета, индексированны к палитре с 8 битами.
DDPF_RGB - RGB данные в структуре формата пикселя имеют силу.
DDPF_RGBTOYUV - Поверхность примет RGB данные и транслирует их в течение операции записи к YUV данным. Формат данных, которые будут записаны, будет содержаться в структуре формата пикселя. Флажок DDPF_RGB будет установлен.
DDPF_YUV YUV данные в структуре формата пикселя имеют силу.
DDPF_ZBUFFER - Формат пикселя описывает поверхность z-буфера.
Я надеюсь, что следующая структура будет последняя на сегодня:
В не ничего особенного нет, через неё мы просто передаём какие-нибудь флаги. С ними ты познакомишься по мере надобности.
Так как при создании поверхности я запросил двойную буферизацию, мне необходимо получить доступ к обоим буферам. Указатель на первый мне вернула функция CreateSurface, а на присоединённый к ней второй буфер я получаю ссылку через функцию GetAttachedSurface :
function GetAttachedSurface(
var lpDDSCaps: TDDSCaps;// Адрес структуры DDSCAPS
out lplpDDAttachedSurface: IdirectDrawSurface//Указатель на вторичную поверхность
): HResult; stdcall;
На сегодня хватит, ты и так наверно уже загружен инфой по самые "нехочу". Со всем остальным, что здесь происходит я познакомлю тебя в следующем номере. А ты пока что поиграй с простеньким примером, который я написал для тебя.