HRESULT EnumDisplayModes(DWORD flags, LPDDSURFACEDESC desc, LPVOID callbackdata, LPDDENUMMODESCALLBACK callback);
Первый аргумент EnumDisplayModes() представляет собой набор флагов для описания дополнительных возможностей. Второй — является указателем на структуру DDSURFACEDESC с описанием необходимых атрибутов видеорежимов. Третий аргумент может использоваться для передачи данных косвенно вызываемой функции при каждом обращении к ней, а четвертый — является указателем на эту функцию. В главе 3 вызов EnumDisplayModes() выглядел так:
ddraw->EnumDisplayModes(0, 0, this, DisplayModeAvailable);
Первый и второй аргументы равны нулю; это означает, что мы не указываем флаги и критерии видеорежимов. Поскольку флаги (первый аргумент) не указаны, каждый обнаруженный видеорежим включается в список один и только один раз. Если бы в первом аргументе был указан флаг DDEDM_REFRESHRATES, каждый видеорежим вошел бы в список столько раз, сколько различных частот смены кадров в нем поддерживается. Такая возможность рассматривается ниже в этой главе при работе с частотами смены кадров в программе SuperSwitch.
Функция ActivateDisplayMode()
Как было сказано в начале главы, смена видеорежима не сводится к вызову функции SetDisplayMode(). Функция SetDisplayMode() активизирует нужный режим, но при этом необходимо уничтожить существующие поверхности и создать их заново. Класс DirectDrawWin решает эту задачу за вас. В него входит функция ActivateDisplayMode(), выполняющая все действия, необходимые для активизации видеорежима и восстановления поверхностей приложения. Для удобства давайте снова посмотрим, как выглядит функция ActivateDisplayMode() (см. листинг 4.1).
Листинг 4.1. Функция DirectDrawWin::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;
}
Функция ActivateDisplayMode() получает один аргумент — индекс в отсортированном списке обнаруженных видеорежимов. Сначала индекс проверяется на правильность. Если он соответствует допустимому элементу массива displaymode, высота, ширина и глубина заданного режима извлекаются из массива и используются для инициализации переменных displayrect и displaydepth. Затем атрибуты видеорежима используются при вызове функции SetDisplayMode(), активизирующей новый видеорежим.
Далее функция CreateFlipingSurfaces() создает первичную поверхность со вторичным буфером, а функция StorePixelFormatData() проверяет, не устарел ли формат пикселей DirectDrawWin (форматы пикселей подробно рассматриваются в главе 5). Наконец, мы вызываем функцию CreateCustomSurfaces(), отвечающую за создание вспомогательных поверхностей приложения.
Итак, функция ActivateDisplayMode() автоматизирует процесс переключения видеорежимов. Сначала она проверяет, будут ли при вызове функции SetDisplayMode() использоваться правильные аргументы, а затем восстанавливает поверхности приложения. Настало время применить ее на практике.
Программа Switch
Программа Switch — полноэкранное приложение DirectDraw, которое выводит меню всех поддерживаемых видеорежимов и позволяет активизировать любой из них. На рис. 4.1 показано, как она выглядит.
Рис. 4.1. Программа Switch
Эта программа представляет собой модифицированное полноэкранное приложение, сгенерированное DirectDraw AppWizard. В программу было добавлено меню, позволяющее выбрать видеорежим (с помощью клавиш со стрелками) и активизировать его (клавишей Enter). Клавиша Escape завершает работу программы. Программа Switch также вычисляет и отображает FPS приложения (количество кадров в секунду).
В программе Switch мы научимся:
• переключать видеорежимы;
• выводить текст на поверхностях DirectDraw;
• рассчитывать FPS приложения;
• работать с цветовыми ключами.
Структура приложения
Программа Switch, как и все остальные программы в этой книге, была создана с помощью DirectDraw AppWizard, так что ее структура покажется вам знакомой, если вы прочитали главу 3. Реализация программы основана на двух классах, SwitchWin и SwitchApp, производных от классов DirectDrawWin и DirectDrawApp соответственно. Класс SwitchApp остался в том виде, в котором он был сгенерирован AppWizard, и потому дальнейшему обсуждению не подлежит.
Класс SwitchWin создает три поверхности: одна используется для перемещения растрового изображения на экране, вторая реализует меню видеорежимов, а третья — вывод FPS. Но перед тем, как рассматривать код программы, давайте поговорим о том, как вывести на поверхность текст и как рассчитать FPS приложения.
Вывод текста
Самый простой способ вывести текст на поверхность — получить у Windows DC (контекст устройства) с помощью функции GetDC() интерфейса DirectDrawSurface. После этого можно вывести текст на поверхность стандартными функциями TextOut() и TextOutExt() и освободить DC функцией DirectDrawSurface::ReleaseDC(). Атрибуты текста (цвет, фон и способ вывода) выбираются следующими функциями Win32:
• SelectObject()
• SetBkMode()
• SetBkColor()
• SetTextColor()
Мы воспользуемся этими функциями в программе Switch для вывода меню видеорежимов и значения FPS.
FPS приложения можно вычислить несколькими способами. Наиболее точный из них — подсчитать общее количество кадров, выведенных приложением, и затем разделить его на время работы приложения в секундах. Недостаток этого способа заключается в том, что значение FPS можно получить лишь после того, как приложение завершится.
Чтобы вычислить FPS во время работы приложения, необходимо производить периодические измерения. К сожалению, скорость работы приложения может изменяться в зависимости от сложности графического вывода и объема внутренних вычислений. Кроме того, Windows замедляет работу приложения при передаче части процессорного времени другим приложениям или при переносе содержимого памяти на диск. В момент запуска приложение обычно работает медленнее, потому что Windows начинает сбрасывать данные на диск. Только после того, как Windows «успокоится» и перестанет работать с диском, можно будет получить достоверные значения FPS.
В программе Switch мы будем периодически вычислять FPS и отображать результат до истечения следующего интервала. Если запустить программу Switch, вы заметите, что значение FPS появляется не сразу; это происходит из-за того, что для получения достоверных показаний должно пройти некоторое время.
Для хронометража используется функция Win32 timeGetTime(), которая возвращает количество миллисекунд, прошедших с момента запуска Windows. В программе Switch функция timeGetTime() вызывается после каждых 100 кадров; значение FPS равно 100, разделенному на количество прошедших секунд.
Функция timeGetTime() не обеспечивает максимальной точности измерений, которую можно получить в Windows (для более точного хронометража можно воспользоваться функцией QueryPerformanceCounter()). Если бы мы отслеживали очень короткие периоды времени (например, интервалы по 10 миллисекунд), то функция timeGetTime() не давала бы приемлемых результатов, но поскольку таймер используется не чаще одного раза в секунду, подходит и timeGetTime().
Класс SwitchWin
Давайте рассмотрим код программы Switch. Начнем с определения класса SwitchWin (см. листинг 4.2).
Листинг 4.2. Объявление класса SwitchWin
class SwitchWin : public DirectDrawWin {
public:
SwitchWin();
protected:
//{{AFX_MSG(SwitchWin)
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
int SelectDriver();
int SelectInitialDisplayMode();
BOOL CreateCustomSurfaces();
void DrawScene();
void RestoreSurfaces();
BOOL CreateMenuSurface();
BOOL UpdateMenuSurface();
BOOL CreateFPSSurface();
BOOL UpdateFPSSurface();
private:
LPDIRECTDRAWSURFACE bmpsurf;
int x, y;
int xinc, yinc;
LPDIRECTDRAWSURFACE menusurf;
int selectmode;
LPDIRECTDRAWSURFACE fpssurf;