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

Инициализация DirectDraw

Инициализация DirectDraw

Фактическое создание окна (вызов функции CreateEx()) заставляет Windows послать нашему приложению сообщение WM_CREATE. Класс DirectDrawWin перехватывает это сообщение в обработчике OnCreate(), созданном ClassWizard (см. листинг 3.1).

Листинг 3.1. Функция DirectDrawWin::OnCreate()

int DirectDrawWin::OnCreate(LPCREATESTRUCT) {
 DirectDrawEnumerate(DriverAvailable, this);
 if (totaldrivers==0) {
  AfxMessageBox("No DirectDraw drivers detected");
  return -1;
 }
 int driverindex=SelectDriver();
 if (driverindex<0) {
  TRACE("No DirectDraw driver selectedn");
  return -1;
 } else if (driverindex>totaldrivers-1) {
  AfxMessageBox("Invalid DirectDraw driver selectedn");
  return -1;
 }
 LPDIRECTDRAW ddraw1;
 DirectDrawCreate(driver[driverindex].guid, &ddraw1, 0);
 HRESULT r;
 r=ddraw1->QueryInterface(IID_IDirectDraw2, (void**)&ddraw2);
 if (r!=S_OK) {
  AfxMessageBox("DirectDraw2 interface not supported");
  return -1;
 }
 ddraw1->Release(), ddraw1=0;
 ddraw2->SetCooperativeLevel(GetSafeHwnd(), DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX);
 ddraw2->EnumDisplayModes(0, 0, this, DisplayModeAvailable);
 qsort(displaymode, totaldisplaymodes, sizeof(DisplayModeInfo),   CompareModes);
 int initmode=SelectInitialDisplayMode();
 if (ActivateDisplayMode(initmode)==FALSE) return -1;
 return 0;
}

Вся инициализация DirectDraw выполняется в функции OnCreate() (при поддержке нескольких вспомогательных функций). Процесс инициализации состоит из семи этапов:

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

• Выбор драйвера DirectDraw.

• Инициализация DirectDraw с использованием выбранного драйвера.

• Получение списка поддерживаемых видеорежимов.

• Выбор исходного видеорежима.

• Активизация выбранного видеорежима.

• Создание поверхностей приложения.

Все эти этапы рассматриваются в последующих разделах.

Получение списка драйверов 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. Строки описаний и имен зависят от типов видеоустройств и версий драйверов.

Выбор драйвера

После того как все драйверы DirectDraw будут перечислены, функция OnCreate() выбирает один из них. Выбор драйвера по умолчанию может быть переопределен в производных классах с помощью виртуальной функции SelectDriver(). Возвращаясь к листингу 3.1, мы видим, что величина, возвращаемая функцией SelectDriver(), используется в качестве индекса массива driver (причем значения индекса начинаются с нуля). Индекс показывает, какой GUID (и, следовательно, драйвер) должен использоваться для инициализации DirectDraw. Версия SelectDriver() из класса DirectDrawWin выглядит так:

virtual int SelectDriver() {
 return 0;
}

По умолчанию SelectDriver() возвращает 0, тем самым показывая, что должно использоваться первичное видеоустройство. Чтобы изменить ее поведение, следует переопределить SelectDriver() в классе, производном от DirectDrawWin. В нашем примере класс BounceWin переопределяет SelectDriver() так, чтобы в случае обнаружения нескольких драйверов выводилось меню:

int bounceWin::SelectDriver() {
 int numdrivers=GetNumDrivers(); 
 if (numdrivers==1) return 0;
 CArray <CString, CString>drivers;
 for (int i=0;i<numdrivers;i++) {
  LPSTR desc, name;
  GetDriverInfo(i, 0, &desc, &name);
  drivers.Add(desc);
 }
 DriverDialog dialog;
 dialog.SetContents(&drivers);
 if (dialog.DoModal()!=IDOK) return -1;
 return dialog.GetSelection();
}

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

Если в системе доступно несколько драйверов, функция SelectDriver() создает меню, состоящее из строк с описаниями драйверов. Класс DriverDialog (простой класс диалогового окна, сгенерированный ClassWizard) выводит диалоговое окно, в котором пользователь может выбрать нужный драйвер. На рис. 3.9 изображено такое окно с двумя строками: первичным драйвером и драйвером DirectX 2 для видеокарты Orchid Righteous 3D.

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

Инициализация DirectDraw

Третья задача, выполняемая функцией OnCreate(), — инициализация DirectDraw. Я снова привожу соответствующий фрагмент листинга 3.1:

LPDIRECTDRAW ddraw1;
DirectDrawCreate(driver[driverindex].guid, &ddraw1, 0);
HRESULT r;
r=ddraw1->QueryInterface(IID_IDirectDraw2, (void**)&ddraw2);
if (r!=S_OK) {
 AfxMessageBox("DirectDraw2 interface not supported");
 return -1;
}
ddraw1->Release(), ddraw1=0;
ddraw2->SetCooperativeLevel(GetSafeHwnd(), DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX);

Сначала мы объявляем ddraw1, указатель на интерфейс DirectDraw. Это локальный и, следовательно, временный указатель. Класс DirectDrawWin объявляет ddraw2, указатель на интерфейс DirectDraw2, однако мы не сможем инициализировать его без интерфейса DirectDraw. Функция DirectDrawCreate() инициализирует указатель ddraw1. Первый аргумент является указателем на GUID выбранного драйвера. Адрес указателя ddraw1 передается в качестве второго аргумента. Последний аргумент DirectDrawCreate() должен быть равен 0.


Рис. 3.9. Диалоговое окно для выбора драйвера

После того как интерфейс DirectDraw будет инициализирован, им можно воспользоваться для получения указателя на интерфейс DirectDraw2. Для этого следует вызвать функцию QueryInterface() и передать ей GUID интерфейса DirectDraw2, определенный под именем IID_IDirectDraw2. Если вызов QueryInterface() заканчивается неудачно, программа выводит диалоговое окно и завершает работу. Фактически мы требуем присутствия библиотеки DirectX версии 2 и выше (потому что интерфейс DirectDraw2 впервые появился в DirectX 2). Если вызов QueryInterface() окажется успешным, указатель ddraw1 освобождается. Попеременный вызов функций интерфейсов DirectDraw и DirectDraw2 не рекомендуется, поэтому освобождение указателя на интерфейс DirectDraw гарантирует, что в оставшейся части кода будет использоваться только интерфейс DirectDraw2.

Затем мы вызываем функцию SetCooperativeLevel() и в качестве аргументов передаем ей логический номер нашего окна и три флага. По логическому номеру организуется взаимодействие окна с DirectDraw. При вызове SetCooperativeLevel() использованы три флага: DDSCL_EXCLUSIVE, DDSCL_FULLSCREEN и DDSCL_ALLOWMODEX. Флаги монопольного и полноэкранного режима обычно используются вместе для получения максимальных полномочий по управлению видеоустройством. Последний флаг означает, что все поддерживаемые видеорежимы Mode X должны быть доступны для выбора в программе. В Windows NT этот флаг игнорируется.

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

На следующем этапе необходимо определить все видеорежимы, поддерживаемые инициализированным драйвером 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.

Выбор видеорежима

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

virtual int SelectInitialDisplayMode() = 0;

В C++ чисто виртуальные функции обязательно должны переопределяться, в противном случае класс не будет компилироваться. Однако со стороны DirectDrawWin было бы нечестно требовать от производного класса выбора исходного видеорежима, не предоставляя ему средств для просмотра возможных вариантов (переменные класса, в которых хранятся сведения о видеорежимах, являются закрытыми (private)). Для этой цели в классе DirectDrawWin предусмотрены функции GetNumDisplayModes() и GetDisplayModeDimensions(). В версии SelectInitialDisplayMode() класса BounceWin эти функции используются для выбора исходного режима:

int BounceWin::SelectInitialDisplayMode() {
 int i, nummodes=GetNumDisplayModes();
 DWORD w,h,d;
 for (i=0;i<nummodes;i++) {
  GetDisplayModeDimensions(i, w, h, d);
  if (w==desiredwidth && h==desiredheight && d==desireddepth) return i;
 }
 for (i=0;i>nummodes;i++)  {
  GetDisplayModeDimensions(i, w, h, d);
  if (d==desireddepth) return i;
 }
 return 0;
}

Функция сначала определяет количество режимов функцией GetNumDisplayModes(), а затем в цикле пытается найти видеорежим с заданным разрешением и глубиной пикселей. Атрибуты каждого видеорежима извлекаются функцией GetDisplayModeDimensions(); если совпадение будет найдено, возвращается индекс видеорежима. В противном случае другой цикл ищет любой видеорежим с заданной глубиной пикселей. Поскольку цикл начинается с начала массива displaymode, с большей вероятностью будут выбираться режимы низкого разрешения. Если не найдено ни одного видеорежима с заданной глубиной пикселей, возвращается значение 0 — оно говорит о том, что следует использовать видеорежим с минимальным разрешением. Код возврата –1 сообщает DirectDrawWin о том, что ни один приемлемый видеорежим так и не был найден и работу приложения следует завершить.

Активизация видеорежима

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

Листинг 3.2. Функция ActivateDisplayMode()

BOOL DirectDrawWin::ActivateDisplayMode(int mode) {
 if (mode<0 || mode>=totaldisplaymodes) return FALSE;
 DWORD width = displaymode[mode].width;
 DWORD height = displaymode[mode].height;
 DWORD depth = displaymode[mode].depth;
 displayrect.left=0;
 displayrect.top=0;
 displayrect.right=width;
 displayrect.bottom=height;
 displaydepth=depth;
 ddraw2->SetDisplayMode(width, height, depth, rate, 0);
 curdisplaymode = mode;
 TRACE("------------------- %dx%dx%d (%dhz) ---------------n",   width, height, depth, rate);
 if (CreateFlippingSurfaces()==FALSE) {
  FatalError("CreateFlippingSurfaces() failed");
  return FALSE;
 }
 StorePixelFormatData();
 if (CreateCustomSurfaces()==FALSE) {
  FatalError("CreateCustomSurfaces() failed");
  return FALSE;
 }
 return TRUE;
}

Нужный видеорежим определяется параметром mode, который сначала проверяется на правильность. Затем его ширина, высота и глубина извлекаются из массива displaymode и заносятся в переменные displayrect и displaydepth. Доступ к этим переменным в производных классах осуществляется с помощью функций GetDisplayRect() и GetDisplayDepth().

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

Перед тем как рассматривать оставшуюся часть функции, следует сделать одно важное замечание. До сих пор, если функция заканчивалась неудачей и требовалось вывести сообщение, можно было использовать функцию MFC AfxMessageBox(). Пока видеорежим не изменялся, все было нормально, но после изменения видеорежима для вывода сообщений и завершения программы применяется функция FatalError(). Эта функция класса DirectDrawWin восстанавливает видеорежим Windows, выводит окно сообщения и завершает программу.

Оглавление книги


Генерация: 1.194. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз