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


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

Уже более пяти лет операционные системы семейства Win32 (Windows 95/98/Me/NT/2000/XP) практически безраздельно властвуют на рынке приложений для платформы Intel. Тем не менее до сих пор многие начинающие программисты не торопятся осваивать Windows. Когда дело касается создания графических приложений, требующих прямого доступа к видеопамяти, они отдают свое предпочтение MS-DOS и низкоуровневым функциям стандартов VGA BIOS и VESA/VBE. Так, и на страницах журнала "Мир ПК" часто можно встретить статьи, посвященные такому подходу. Между тем есть достаточно простой способ решения данных задач средствами Win32, а именно использование части технологии Microsoft DirectX - компонента DirectDraw.

О выборе языка программирования

Объект DirectDraw представляет собой обычный COM-объект, порожденный непосредственно от базового интерфейса IUnknown. Напомню, что COM-объекты очень похожи на обычные объекты языка Cи++, но отличаются рядом ограничений. Так, COM-объекты не могут иметь открытых переменных (полей), конструкторов и деструкторов. Для создания COM-объектов обычно используются специальные функции, а для их удаления применяется метод Release(), принадлежащий базовому интерфейсу IUnknown. Поскольку COM-интерфейсы довольно легко реализуются средствами Cи++, в данной статье выбран именно этот язык программирования. Разумеется, работать с объектами DirectX позволяют и другие популярные языки, например Паскаль (Borland Delphi [3], TMT Pascal [4]) и Visual Basic. Стандартный язык программирования Си также позволяет работать с COM-объектами, но при этом значительно усложняется синтаксис вызова функций DirectDraw, и потому при выборе транслятора следует ориентироваться на Microsoft Visual C++ 6.x.

Создание базового объекта DirectDraw

Первый шаг к применению компонента DirectDraw - реализация базового DirectDraw-объекта при помощи функции DirectDrawCreate(), обычно находящейся в динамической библиотеке DDRAW.DLL и объявленной в заголовочном файле ddraw.h:

LPDIRECTDRAW lpDDraw;
hResult = DirectDrawCreate(NULL, &lpDDraw,
NULL);

Первый параметр функции DirectDrawCreate() является указателем на GUID (Globally Unique Identifier), определяющим создаваемый драйвер. Чаще всего в качестве этого параметра применяется константа NULL, идентифицирующая активное графическое устройство (Active Display Device). В случае удачного вызова функция возвращает значение DD_OK и помещает указатель на порожденный объект DirectDraw в переменную lpDDraw. Последний параметр функции DirectDrawCreate() предназначен для COM-агрегирования (COM aggregation), реализующего разделение интерфейсов многократного использования при наследовании одним объектом методов другого. В текущей версии DirectX этот параметр должен иметь значение NULL.

Полученный указатель на объект DirectDraw теперь можно применять для задания прав доступа к экрану (Cooperative Level) и установки графического режима (Display Mode). Права эти задаются с помощью метода SetCooperativeLevel(). Обычно требуется задавать полноэкранный режим с исключительными правами доступа. Делается это следующим образом:

hResult = pDDraw->SetCooperativeLevel
(hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

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

Установка графического режима

Теперь настало время заняться установкой графического режима. Эта задача решается при помощи метода SetDisplayMode(), аналога функции 02h прерывания Int 13h VESA/VBE, но в отличие от нее позволяет напрямую задавать горизонтальное и вертикальное разрешение экрана, а также максимальное количество цветов, доступное в данном режиме. Например, установка 256-цветного режима с разрешением 640x480 точек будет иметь вид:

hResult = lpDDraw->SetDisplayMode(640, 480, 8);

Следующая же функция устанавливает режим 800x600 точек, поддерживающий уже 65 536 цветов.

hResult = lpDDraw->SetDisplayMode(800, 600, 16);

Здесь следует отметить, что современные версии пакета DirectX содержат новые компоненты DirectDraw2, DirectDraw4 и DirectDraw7, обладающие более широкими возможностями, чем рассматриваемый компонент DirectDraw. Так, они позволяют устанавливать вертикальную частоту развертки монитора, возвращают объем видеопамяти и т.п. Однако для данной статьи мы возьмем именно DirectDraw, поскольку он обладает всеми свойствами, необходимыми для решения поставленной задачи, в частности для получения прямого доступа к видеопамяти в оконных Win32-приложениях.

Создание первичной поверхности

Следующий шаг - создание первичной поверхности (Primary Surface), являющейся объектом DirectDrawSurface. Здесь следует отметить, что компонент DirectDraw дает возможность порождать множество объектов DirectDrawSurface. Чтобы упростить пример, мы заведем всего одну поверхность DirectDrawSurface, отображенную непосредственно на область видеопамяти для получения прямого доступа к ней.

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

typedef struct _DDSURFACEDESC {
DWORD dwSize;
DWORD dwFlags;
DWORD dwHeight;
DWORD dwWidth;
union
{
LONG lPitch;
DWORD dwLinearSize;
};
DWORD dwBackBufferCount;
union
{
DWORD dwMipMapCount;
DWORD dwZBufferBitDepth;
DWORD dwRefreshRate;
};
DWORD dwAlphaBitDepth;
DWORD dwReserved;
LPVOID lpSurface;
DDCOLORKEY ddckCKDestOverlay;
DDCOLORKEY ddckCKDestBlt;
DDCOLORKEY ddckCKSrcOverlay;
DDCOLORKEY ddckCKSrcBlt;
DDPIXELFORMAT ddpfPixelFormat;
DDSCAPS ddsCaps;
} DDSURFACEDESC;

Полное описание всех полей этой структуры можно найти в работе [2]. Нам же понадобится инициализировать три поля:

  • dwSize - размер структуры в байтах, который должен быть инициализирован перед ее использованием;
  • dwFlags - набор флагов, определяющих, какие именно поля структуры содержат инициализированные значения;
  • ddsCaps - вложенная структура типа DDSCAPS.

Структура DDCAPS также определена в файле ddraw.h:

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

Первичная поверхность создается при помощи метода CreateSurface(), принадлежащего объекту DirectDraw:

DDSURFACEDESC ddsd;
DDSCAPS ddsc;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.ddsCaps.dwCaps = DDSCAPS
_PRIMARYSURFACE;
ddsd.dwFlags = DDSD_CAPS;
hResult = (lpDDraw->CreateSurface
(&ddsd, &lpPrimarySurface, NULL);

Обратите внимание на то, что перед вызовом метода CreateSurface() все неиспользуемые поля структуры типа DDSCAPS должны быть обнулены, а поле dwSize содержать ее точный размер в байтах.

Метод lock()

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

hResult = lpPrimarySurface->Lock
(NULL, &ddsd, DDLOCK_WAIT, NULL);

В качестве первого аргумента Lock() получает указатель на структуру типа RECT, содержащую координаты прямоугольной области, к которой мы хотим иметь прямой доступ. В приведенном выше примере вместо указателя на структуру RECT мы взяли значение NULL. Значит, мы хотим, чтобы у нас появился прямой доступ ко всей поверхности, а не к какой-то отдельной ее части. Указатель на саму поверхность передается вторым аргументом метода Lock().

Флаг DDLOCK_WAIT сообщает методу Lock() о том, что при неудачной попытке блокирования поверхности (например, во время проведения blit-операции) следует повторять эту операцию вплоть до возникновения какой-либо иной ошибки типа DDERR_SURFACEBUSY и т.п.

После успешного выполнения (hResult = DD_OK) метод Lock() заполняет структуру ddsd необходимыми нам параметрами блокируемой поверхности. В данном случае это будут параметры DirectDraw-поверхности, отражаемой на активную страницу видеопамяти. Итак, наиболее важными полями структуры ddsd считаются:

  • lpSurface - указатель на область памяти, ассоциированную с поверхностью. Здесь он указывает на начало активной страницы видеопамяти;
  • lPitch - расстояние в байтах до начала следующей графической линии; данная величина может быть больше или равна "физической" ширине поверхности (dwWidth), умноженной на размер точки (пиксела) в байтах (BPP).

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

memset(lpSurface, 0, ddsd.lPitch
* ddsd.dwHeight);

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

lpPrimarySurface->Unlock
(ddsd.lpSurface);

В противном случае операционная система может зависнуть.

 
« Предыдущая статья   Следующая статья »