• Переключение видеорежимов
  • Обнаружение видеорежимов и частот смены кадров
  • Вычисление FPS
  • Графический вывод
  • Обработка пользовательского ввода 
  • Частота смены кадров 
  • Глава 4. Видеорежимы и частота смены кадров

    В главе 1 я упоминал функции EnumDisplayModes() и SetDisplayMode() интерфейса DirectDraw и говорил о том, что они применяются для обнаружения и активизации видеорежимов. Здесь эти функции будут применены на практике. Сначала мы познакомимся с общими принципами переключения видеорежимов, а затем рассмотрим две демонстрационных программы: Switch и SuperSwitch. Программа Switch выводит меню с обнаруженными видеорежимами и позволяет последовательно активизировать каждый из них. Программа SuperSwitch в дополнение к этому позволяет выбрать частоту смены кадров для каждого видеорежима.

    Перед тем как переходить к программированию, мне хотелось бы сказать, что в этой главе нет ничего сложного. Управление видеорежимами — одна из простейших возможностей DirectDraw, да и с частотой смены кадров разобраться не так уж тяжело. Следовательно, я вовсе не собираюсь поразить вас сообщением о том, что в DirectDraw можно переключать видеорежимы. Вместо этого мы рассмотрим проблемы, связанные с переключением режимов, а заодно рассеем некоторые распространенные заблуждения. Материал этой главы поможет вам писать приложения с простым и надежным переключением видеорежимов.

    Кроме того, эта глава преследует и другую цель. В программах Switch и SuperSwitch используются некоторые средства DirectDraw, которые, скорее всего, вам пригодятся (или уже пригодились), — например, цветовые ключи и вывод текста на поверхности. Хотя у нас нет времени для подробного изучения этих тем, мы кратко рассмотрим их в этой главе, чтобы в дальнейшем никакие объяснения уже не понадобились.

    Переключение видеорежимов

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

    Тем не менее переключение видеорежимов не сводится к простому вызову функции SetDisplayMode(). По этой причине в класс DirectDrawWin была включена функция ActivateDisplayMode(), которая выполняет все необходимые действия для гладкого перехода от одного режима к другому. Вскоре мы рассмотрим ActivateDisplayMode() и все, что она делает, но сначала давайте обратимся к самой функции SetDisplayMode().

    Функция SetDisplayMode()

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

    HRESULT SetDisplayMode(DWORD width, DWORD height, DWORD depth);

    СОВЕТ

    Для любознательных читателей

    Заглянув в заголовочный файл DirectDraw (ddraw.h), вы не найдете в нем этого прототипа. Это объясняется тем, что все функции DirectDraw описываются на IDL (языке определения интерфейсов) спецификации COM. На IDL функция SetDisplayMode() выглядит так:

    STDMETHOD(SetDisplayMode)(THIS_ DWORD, DWORD, DWORD) PURE;

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

    Функция SetDisplayMode(), как и большинство функций DirectX API, возвращает значение типа HRESULT — 32-разрядную величину с описанием результата вызова функции. Ее значение DD_OK показывает, что вызов оказался успешным.

    Версия SetDisplayMode() из интерфейса DirectDraw получает три аргумента типа DWORD. Эти аргументы определяют разрешение экрана и глубину пикселей нужного видеорежима, поэтому стандартный видеорежим VGA 640×480×8 активизируется так:

    ddraw1->SetDisplayMode(640, 480, 8);

    Выглядит довольно просто, поэтому давайте перейдем к версии SetDisplayMode() из интерфейса DirectDraw2. Ее традиционный прототип выглядит так:

    HRESULT SetDisplayMode(DWORD width, DWORD height, DWORD depth,  DWORD refreshrate, DWORD flags);

    В этой версии появляются два дополнительных аргумента: частота смены кадров и двойное слово, которое может быть использовано в будущих версиях DirectDraw, а пока должно быть равно нулю. В расширенной версии SetDisplayMode() стандартный видеорежим VGA 640×480×8 можно активизировать так:

    ddraw1->SetDisplayMode(640, 480, 8, 0, 0);

    В данном случае вместо частоты смены кадров передается 0; это означает, что должна быть использована частота, принятая по умолчанию. Кроме того, можно указать конкретное значение частоты (60 Гц в следующем примере):

    ddraw1->SetDisplayMode(640, 480, 8, 60, 0);

    Однако не следует думать, что вы можете задать любую частоту (или другие параметры видеорежима). Перед тем как вызывать SetDisplayMode(), необходимо сначала определить параметры и частоты допустимых видеорежимов.

    Обнаружение видеорежимов и частот смены кадров

    В главе 3 говорилось о том, как функция EnumDisplayModes() интерфейса DirectDraw перечисляет все поддерживаемые видеорежимы. Через косвенно вызываемую функцию она сообщает вашему приложению о каждом видеорежиме, поддерживаемом установленными видеоустройствами. Прототип функции EnumDisplayModes() выглядит так:

    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 можно получить лишь после того, как приложение завершится.

    Чтобы вычислить 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;

     RECT fpsrect;

     BOOL displayfps;

     DWORD framecount;

     HFONT smallfont, largefont;

    };

    Класс SwitchWin содержит всего одну открытую (public) функцию — конструктор класса (вскоре мы его рассмотрим). В классе также присутствует функция OnKeyDown() — обработчик сообщений, созданный ClassWizard (закомментированные директивы AFX, окружающие функцию OnKeyDown(), используются ClassWizard для поиска функций-обработчиков). Мы воспользуемся этой функцией для обработки нажимаемых клавиш — стрелок, Enter и незаменимой клавиши Escape.

    Следующие пять функций являются переопределенными версиями функций DirectDrawWin:

    • SelectDriver()

    • SelectInitialDisplayMode()

    • CreateCustomSurfaces()

    • DrawScene()

    • RestoreSurfaces()

    С помощью функции SelectDriver() приложение выбирает используемое видеоустройство (если их несколько). Она полностью совпадает со стандартной версией, создаваемой AppWizard, и выводит меню при наличии нескольких драйверов. Функция SelectInitialDisplayMode() задает исходный видеорежим, устанавливаемый приложением. Здесь снова используется стандартная версия AppWizard, которая ищет видеорежим с параметрами 640x480x16.

    Функция CreateCustomSurfaces() вызывается DirectDrawWin при активизации нового видеорежима; мы воспользуемся этой функцией для создания и подготовки поверхностей программы Switch. Функция DrawScene() отвечает за обновление экрана; она будет использоваться для отображения анимации, меню видеорежимов и значения FPS. Наконец, функция RestoreSurfaces() вызывается классом DirectDrawWin при необходимости восстановить потерянные поверхности. Эта функция восстанавливает не только сами поверхности, но и (для особо важных поверхностей) их содержимое.

    Затем класс SwitchWin объявляет четыре функции, специфические для программы Switch:

    • CreateMenuSurface()

    • UpdateMenuSurface()

    • CreateFPSSurface()

    • UpdateFPSSurface()

    Это вспомогательные функции для работы с поверхностями, используемыми для вывода меню видеорежимов и значения FPS. Они подробно рассматриваются ниже в этой главе.

    Закрытые переменные, объявленные в конце, предназначены для отображения анимации, меню видеорежимов и FPS, а также для работы со шрифтами средствами Win32.

    Переменная bmpsurf — указатель на интерфейс DirectDrawSurface, через который мы будем обращаться к данным перемещаемого растра, а переменные x, y, xinc и yinc определяют его положение.

    Указатель menusurf используется для доступа к поверхности меню видеорежимов, а в целой переменной selectmode хранится индекс текущего активного видеорежима.

    Следующие переменные списка связаны с выводом значения FPS. Переменная fpssurf — указатель на интерфейс DirectDrawSurface, через который производится доступ к поверхности FPS. Структура типа RECT (fpsrect) содержит размеры поверхности fpssurf. Логическая переменная displayfps управляет отображением значения FPS, а в переменной framecount хранится количество кадров, выведенных в очередном временном интервале измерения FPS.

    Две последние переменные, smallfont и largefont, имеют тип HFONT. Это логические номера шрифтов Win32, используемые для вывода текста на поверхностях menusurf и fpssurf.

    Инициализация приложения

    Наше знакомство с программой Switch начинается с конструктора switchWin, внутри которого происходит первоначальная инициализация переменных класса. Не следует путать эту инициализацию с той, что выполняется функцией CreateCustomSurfaces(), потому что в отличие конструктора CreateCustomSurfaces() вызывается при каждой смене видеорежима. Конструктор выглядит так

    SwitchWin::SwitchWin(){

     bmpsurf=0;

     x=y=0;

     xinc=8;

     yinc=1;

     menusurf=0;

     fpssurf=0;

     vlargefont = CreateFont(28, 0, 0, 0,    FW_NORMAL, FALSE, FALSE, FALSE,   ANSI_CHARSET,    OUT_DEFAULT_PRECIS,   CLIP_DEFAULT_PRECIS,    DEFAULT_QUALITY,   VARIABLE_PITCH,    "Arial");

     smallfont = CreateFont(14, 0, 0, 0,    FW_NORMAL, FALSE, FALSE, FALSE,   ANSI_CHARSET,    OUT_DEFAULT_PRECIS,   CLIP_DEFAULT_PRECIS,    DEFAULT_QUALITY,   VARIABLE_PITCH,    "Arial");

    }

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

    После того как объект SwitchWin будет создан, DirectDrawWin вызывает функции SelectDriver() и SelectInitialDisplayMode(). Поскольку в программе Switch обе функции ведут себя стандартным образом (как описано в главе 3), мы не будем их рассматривать.

    Затем класс DirectDrawWin вызывает функцию SwitchWin::CreateCustomSurfaces(), в которой подготавливает три поверхности, используемые программой Switch:

    BOOL SwitchWin::CreateCustomSurfaces() {

     int displaydepth=GetCurDisplayDepth();

     CString filename;

     if (displaydepth==8) filename="tri08.bmp";

     else filename="tri24.bmp";

     bmpsurf=CreateSurface(filename, TRUE);

     if (bmpsurf==0) {

      TRACE("surface creation failed\n");

      return FALSE;

     }

     selectmode=GetCurDisplayMode();

     CreateMenuSurface();

     UpdateMenuSurface();

     CreateFPSSurface();

     return TRUE;

    }

    Содержимое одной из этих трех поверхностей определяется BMP-файлом. Функция CreateCustomSurfaces() по текущей глубине пикселей определяет, какой из двух BMP-файлов нужно использовать. Затем указатель на поверхность (bmpsurf) инициализируется функцией DirectDrawWin::CreateSurface(). В случае 8-битного видеорежима содержимое палитры DirectDraw определяется палитрой из BMP-файла.

    Затем происходит инициализация самой поверхности и переменных, связанных с видеорежимом. Переменной selectmode присваивается значение, зависящее от текущего видеорежима. Это значение используется для выделения активного видеорежима в меню. Указатель на поверхность меню видеорежимов (menusurf) инициализируется вызовами функций CreateMenuSurface() и UpdateMenuSurface().

    Наконец, переменные поверхности FPS инициализируются функцией Create FPSSurface(). Мы рассмотрим ее позднее, после функций CreateMenuSurface() и UpdateMenuSurface().

    Функция CreateMenuSurface() выглядит так:

    BOOL SwitchWin::CreateMenuSurface() {

     if (menusurf) menusurf->Release(), menusurf=0;

     menusurf=CreateSurface(menuwidth, menuheight);

     if (menusurf==0) Fatal("SwitchWin::CreateMenuSurface() failed\n");

     DDCOLORKEY ddck;

     ddck.dwColorSpaceLowValue = 0;

     ddck.dwColorSpaceHighValue = 0;

     menusurf->SetColorKey(DDCKEY_SRCBLT, &ddck);

     return TRUE;

    }

    Прежде всего CreateMenuSurface() освобождает любые поверхности, созданные ранее. Новая поверхность создается функцией CreateSurface(). Доступ к ней осуществляется через переменную menusurf. Затем мы назначаем новой поверхности цветовой ключ с помощью структуры DDCOLORKEY и функции SetColorKey() интерфейса DirectDrawSurface.

    Если вы не знаете, для чего нужны цветовые ключи, попробуйте запустить программу Switch и понаблюдать за поведением меню видеорежимов. Обратите внимание — когда перемещающийся растр оказывается в верхней части экрана, он проходит как бы позади меню, но при этом остается видимым. Текст меню непрозрачен, однако те части меню, в которых текста нет, прозрачны. Дело в том, что пиксели пустых участков меню не выводятся DirectDraw и потому не заслоняют растр. Цветовой ключ определяет, какие именно пиксели поверхности не будут выводиться.

    Мы назначаем цветовой ключ поверхности меню с помощью структуры DDCOLORKEY и функции SetColorKey(). Оба поля DDCOLORKEY обнуляются (некоторые видеокарты позволяют задавать интервалы цветовых ключей, но в нашем случае используется всего один цвет). Это означает, что пиксели поверхности, равные нулю, не будут копироваться при блит-операциях с активным цветовым ключом.

    После того как поверхность меню будет создана функцией CreateMenuSurface(), она заполняется с помощью функции UpdateMenuSurface(). Внутри последней для вывода текста на поверхность используются функция GetDC() интерфейса DirectDrawSurface и текстовые функции Win32. Функция UpdateMenuSurface() приведена в листинге 4.3.


    Листинг 4.3. Функция SwitchWin::UpdateMenuSurface()

    BOOL SwitchWin::UpdateMenuSurface() {

     char buf[40];

     int len;

     int hdrlen=strlen(headertext);

     ClearSurface(menusurf, 0);

     HDC hdc;

     menusurf->GetDC(&hdc);

     SelectObject(hdc, largefont);

     SetBkMode(hdc, TRANSPARENT);

     SetTextColor(hdc, textshadow);

     TextOut(hdc, 1, 1, headertext, hdrlen);

     SetTextColor(hdc, textcolor);

     TextOut(hdc, 0, 0, headertext, hdrlen);

     SelectObject(hdc, smallfont);

     int nmodes=GetNumDisplayModes();

     if (nmodes>maxmodes) nmodes=maxmodes;

     int rows=nmodes/menucols;

     if (nmodes%menucols) rows++;

     for (int i=0; i<nmodes; i++) {

      int x=(i/rows)*colwidth+2;

      int y=(i%rows)*rowheight+reservedspace;

      DWORD w,h,d;

      GetDisplayModeDimensions(i, w, h, d);

      len=sprintf(buf, "%dx%dx%d", w, h, d);

      SetTextColor(hdc, textshadow);

      TextOut(hdc, x+1, y+1, buf, len);

      if (i==selectmode) SetTextColor(hdc, brighttextcolor);

      else SetTextColor(hdc, textcolor);

      TextOut(hdc, x, y, buf, len);

     }

     len=sprintf(buf, "[Arrows] [Enter] [Escape]");

     SetTextColor(hdc, textshadow);

     TextOut(hdc, 3, 186, buf, len);

     SetTextColor(hdc, textcolor);

     TextOut(hdc, 2, 185, buf, len);

     menusurf->ReleaseDC(hdc);

     return TRUE;

    }

    Функция UpdateMenuSurface() вызывает ClearSurface() и передает ей в качестве аргументов указатель menusurf и 0. В результате все пиксели поверхности обнуляются. Так как ноль является цветовым ключом для данной поверхности, вся поверхность становится прозрачной.

    Теперь все готово к выводу текста. Обратите внимание на функцию SetBkMode(), которая указывает, что текст должен выводиться в прозрачном режиме. Это значит, что функция TextOut() будет выводить только сам текст, без фона, благодаря чему наш прозрачный фон останется в неприкосновенности. Цвет текста задается функцией Win32 SetTextColor(). В этой программе используются три цвета: первый — для обычного текста, второй — для затененного текста, и третий — для текста, выделенного подсветкой. Каждая текстовая строка выводится дважды — сначала затемненным, а потом обычным цветом; затененный текст смещен на один пиксель по отношению к обычному. После завершения вывода текста вызывается функция ReleaseDC() интерфейса DirectDrawSurface.

    Инициализация приложения завершается вызовом функции CreateFPSSurface(), которая создает поверхность для вывода FPS. Она выглядит так:

    BOOL SwitchWin::CreateFPSSurface() {

     static const char dummystr[]="000 FPS";

     HDC hdc = ::GetDC(0);

     SelectObject(hdc, smallfont);

     SIZE size;

     GetTextExtentPoint(hdc, dummystr, strlen(dummystr), &size);

     ::ReleaseDC(0, hdc);

     fpsrect.left=0;

     fpsrect.top=0;

     fpsrect.right=size.cx+1;

     fpsrect.bottom=size.cy+1;

     fpssurf=CreateSurface(fpsrect.right, fpsrect.bottom);

     DDCOLORKEY ddck;

     ddck.dwColorSpaceLowValue = 0;

     ddck.dwColorSpaceHighValue = 0;

     fpssurf->SetColorKey(DDCKEY_SRCBLT, &ddck);

     framecount=0;

     displayfps=FALSE;

     return TRUE;

    }

    Работа CreateFPSSurface() начинается с определения размера поверхности функцией GetTextExtentPoint(). Фиктивная строка (с текстом, который занимает максимальную возможную площадь) передается в качестве аргумента функции GetTextExtentPoint(), вычисляющей размеры области (в пикселях) для вывода заданного текста. По размерам, полученным от GetTextExtentPoint(), мы определяем размеры поверхности, добавляя один пиксель для смещения тени. Такой подход отличается от использованного в функции CreateMenuSurface(), потому что этот код автоматически регулирует размеры поверхности при изменении размера шрифта. Поверхность меню, напротив, обладает фиксированными размерами, не зависящими от размера шрифта.

    По аналогии с menusurf мы обеспечиваем прозрачность, назначая поверхности нулевой цветовой ключ (с помощью функции SetColorKey() интерфейса DirectDrawSurface). Наконец, переменная framecount (предназначенная для подсчета кадров за текущий интервал хронометража) обнуляется, а логической переменной displayfps присваивается значение FALSE, согласно которому поверхность FPS пока не должна отображаться на экране.

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

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

    Графический вывод

    Графическим выводом в программе Switch занимается функция SwitchWin::DrawScene(). Она отвечает за подготовку кадра во вторичном буфере и переключение страниц, благодаря которому новый кадр отображается на экране. Код функции DrawScene() содержится в листинге 4.4.


    Листинг 4.4. Функция SwitchWin::DrawScene()

    void SwitchWin::DrawScene() {

     ClearSurface(backsurf, 0);

     BltSurface(backsurf, bmpsurf, x, y);

     x+=xinc;

     y+=yinc;

     const CRect& displayrect=GetDisplayRect();

     if (x<-160 || x>displayrect.right-160) {

      xinc=-xinc;

      x+=xinc;

     }

     if (y<-100 || y>displayrect.bottom-100)  {

      yinc=-yinc;

      y+=yinc;

     }

     backsurf->BltFast(0, 0, menusurf, 0,    DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

     UpdateFPSSurface();

     if (displayfps) {

      int x=displayrect.right-fpsrect.right-1;

      int y=displayrect.bottom-fpsrect.bottom-1;

      backsurf->BltFast(x, y, fpssurf, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT );

     }

     primsurf->Flip(0, DDFLIP_WAIT);

    }
     

    Сначала функция DrawScene() подготавливает вторичный буфер, стирая его содержимое функцией ClearSurface(). Мы заполняем вторичный буфер нулями, но, поскольку он не имеет цветового ключа, 0 в данном случае имеет иной смысл, чем для поверхностей menusurf и fpssurf. Для вторичных буферов 0 означает черный цвет (в большинстве случаев).

    СОВЕТ

    Черный цвет не гарантирован 

    По умолчанию DirectDraw резервирует два элемента палитры: для черного (индекс 0) и для белого (индекс 255). Поэтому обычно заполнение поверхности нулями равносильно ее заливке черным цветом. Тем не менее в палитрах, созданных с флагом DDSCAPS_ALLOW256, можно задавать все 256 элементов.

    Функция DirectDrawWin::CreateSurface() при создании и установке палитры (когда необязательный аргумент use_palette равен TRUE) использует флаг DDSCAPS_ALLOW256, поэтому первый элемент в палитрах наших приложений может быть отличен от черного цвета. Флаг можно удалить, но это нарушит цветопередачу при отображении BMP-файлов, у которых первый и последний элементы палитры отличны от черного и белого цветов соответственно.

    В программах этой книги используются BMP-файлы, для которых положение черного и белого цвета в палитре совпадает с принятым в DirectDraw по умолчанию. В этом случае растры будут правильно отображаться независимо от флага DDSCAPS_ALLOW256.

    Другая причина, по которой первому элементу палитры следует назначать черный цвет, в том, что первый элемент палитры совпадает с цветом, используемым на экране за пределами нормальной области рисования (overscan color — цвет внешней рамки). Хотя не существует никаких формальных причин, по которым этим цветом должен быть именно черный, другие варианты обычно выглядят довольно странно. 

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

    Затем копируется поверхность меню. Она всегда выводится в левом верхнем углу экрана, а ее размеры совпадают с размерами видеорежима с наименьшим разрешением (320×200), так что отсечение не понадобится. Следовательно, мы можем воспользоваться функцией BltFast() интерфейса DirectDrawSurface. Первые два аргумента BltFast() определяют область приемника для наложения копии (оба аргумента равны нулю, что означает левый верхний угол). Третий аргумент является указателем на поверхность-источник, а четвертый описывает копируемую прямоугольную область источника. Вместо прямоугольника мы передаем 0, тем самым показывая, что копироваться должна вся поверхность.

    В последний аргумент функции BltFast() включены флаги DDBLTFAST_SRCCOLORKEY и DDBLTFAST_WAIT. Первый флаг активизирует цветовой ключ поверхности-источника. Если бы он не был указан, то во время блиттинга цветовой ключ поверхности menusurf был бы проигнорирован, а пиксели с нулевыми значениями выводились бы черным цветом. Второй флаг показывает, что выход из функции BltFast() должен произойти лишь после завершения копирования.

    Вернемся к функции DrawScene(). Наша следующая задача - обновление и отображение поверхности FPS (fpssurf). Однако, как было сказано выше, поверхность fpssurf обновляется лишь после того, как закончится очередной интервал хронометража.

    Вычисление FPS и подготовка поверхности осуществляются функцией UpdateFPSSurface(), вызываемой функцией DrawScene() при каждом обновлении экрана. Функция UpdateFPSSurface() выглядит так:

    BOOL SwitchWin::UpdateFPSSurface() {

     static const long interval=100;

     framecount++;

     if (framecount==interval) {

      static DWORD timenow;

      static DWORD timethen;

      timethen=timenow;

      timenow=timeGetTime();

      double seconds=double(timenow-timethen)/(double)1000;

      int fps=(int)((double)framecount/seconds);

      static char buf[10];

      int len=sprintf(buf, "%d FPS", fps);

      ClearSurface(fpssurf, 0);

      HDC hdc;

      fpssurf->GetDC(&hdc);

      SelectObject(hdc, smallfont);

      SetBkMode(hdc, TRANSPARENT);

      SetBkColor(hdc, RGB(0,0,0));

      SetTextColor(hdc, textshadow);

      TextOut(hdc, 1, 1, buf, len);

      SetTextColor(hdc, brighttextcolor);

      TextOut(hdc, 0, 0, buf, len);

      fpssurf->ReleaseDC(hdc);

      displayfps=TRUE;

      framecount=0;

     }

     return TRUE;

    }

    Функция UpdateFPSSurface() использует переменную framecount для подсчета выведенных кадров. Переменная framecount обнуляется в двух случаях: при изменении видеорежима и при обновлении поверхности fpssurf заново вычисленным значением FPS.

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

    Значение FPS преобразуется в строку и выводится на поверхность FPS (после предварительной очистки поверхности функцией ClearSurface()). После вывода текста переменная framecount обнуляется, и начинается новый интервал хронометража. Наконец, переменной displayfps присваивается значение TRUE; оно говорит о том, что на поверхности FPS находится допустимое значение, которое следует вывести на экран.

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

    if (displayfps) {

     int x=displayrect.right-fpsrect.right-1;

     int y=displayrect.bottom-fpsrect.bottom-1;

     backsurf->BltFast(x, y, fpssurf, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

    }

    primsurf->Flip(0, DDFLIP_WAIT);

    Если флаг displayfps равен TRUE, поверхность FPS следует вывести на экран. Однако сначала мы рассчитываем ее положение по известным размерам видеорежима и поверхности. Затем мы копируем поверхность fpssurf функцией BltFast(), после чего выводим на экран вторичный буфер функцией Flip() интерфейса DirectDrawSurface.

    Задача функции DrawScene() выполнена — все три поверхности программы Switch выведены на экран. Тем не менее изучение приложения еще не закончено. Мы должны рассмотреть обработку пользовательского ввода.

    Но перед тем как продолжить, я должен сделать одно замечание относительно программы Switch. Как мы видели во время рассмотрения ее кода, две из трех поверхностей программы имеют цветовые ключи для отображения прозрачных пикселей. Однако при запуске программы кажется, что анимационная поверхность (не имеющая цветового ключа) тоже является прозрачной. Почему? Потому что цвет фона растра (черный) совпадает с цветом вторичного буфера. Если изменить значение для заливки вторичного буфера, станет ясно, что анимационная поверхность на самом деле непрозрачна. 

    Обработка пользовательского ввода 

    При запуске программы Switch текст в нижней части меню подсказывает, какие клавиши управляют работой приложения. Клавиши со стрелками изменяют текущий выделенный видеорежим, клавиша Enter активизирует его (если он не является текущим), а клавиша Escape завершает работу программы. Все эти клавиши обрабатываются функцией OnKeyDown(), создаваемой ClassWizard. Ее код приведен в листинге 4.5.


    Листинг 4.5. Функция SwitchWin::OnKeyDown()

    void SwitchWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {

     int newindex;

     int nmodes=GetNumDisplayModes();

     if (nmodes>maxmodes)  nmodes=maxmodes;

     int rows=nmodes/menucols;

     if (nmodes%menucols)  rows++;

     switch (nChar) {

     case VK_ESCAPE:

      PostMessage(WM_CLOSE);

      break;

     case VK_UP:

      newindex=selectmode-1;

      if (newindex>=0) {

       selectmode=newindex;

       UpdateMenuSurface();

      }

      break;

     case VK_DOWN:

      newindex=selectmode+1;

      if (newindex<nmodes)  {

       selectmode=newindex;

       UpdateMenuSurface();

      }

      break;

     case VK_LEFT:

      newindex=selectmode-rows;

      if (newindex>=0) {

       selectmode=newindex;

       UpdateMenuSurface();

      }

      break;

     case VK_RIGHT:

      newindex=selectmode+rows;

      if (newindex<nmodes) {

       selectmode=newindex;

       UpdateMenuSurface();

      }

      break;

     case VK_RETURN:

      if (selectmode != GetCurDisplayMode()) {

       ActivateDisplayMode(selectmode);

       x=y=0;

      }

      break;

     case 'S':

      SaveSurface(primsurf, "switch.bmp");

      break;

     case 'M':

      SaveSurface(menusurf, "menusurf.bmp");

      break;

     case 'F':

      SaveSurface(fpssurf, "fpssurf.bmp");

      break;

     case 'T':

      SaveSurface(bmpsurf, "trisurf.bmp");

      break;

     default:

      DirectDrawWin::OnKeyDown(nChar, nRepCnt, nFlags);

     }

    }

    Обработка нажатых клавиш происходит в различных секциях оператора switch. Клавиша Escape (код виртуальной клавиши VK_ESCAPE) приводит к посылке сообщения WM_CLOSE и последующему завершению приложения. При нажатии клавиш со стрелками изменяется индекс текущего видеорежима и вызывается функция UpdateMenuSurface(), которая перерисовывает menusurf в соответствии с произведенными изменениями. При нажатии клавиши Enter (VK_RETURN) вызывается функция ActivateDisplayMode(), которой в качестве аргумента передается индекс режима (при условии, что выбран видеорежим, отличный от текущего). Все остальные клавиши, нажатые пользователем, обрабатываются функцией OnKeyDown() базового класса. 

    Восстановление поверхностей 

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

    void SwitchWin::RestoreSurfaces() {

     int displaydepth=GetCurDisplayDepth();

     CString filename;

     if (displaydepth==8) filename="tri08.bmp";

     else filename="tri24.bmp";

     if (bmpsurf->IsLost()) {

      bmpsurf->Restore();

      LoadSurface(bmpsurf, filename);

     }

     if (menusurf->IsLost()) {

      menusurf->Restore();

      UpdateMenuSurface();

     }

     if (fpssurf->IsLost()) {

      fpssurf->Restore();

      ClearSurface(fpssurf, 0);

     }

     displayfps=FALSE;

     framecount=0;

    }

    В нашем случае функция RestoreSurfaces() отвечает за восстановление всех трех вспомогательных поверхностей. Ее работа начинается с анимационной поверхности (bmpsurf). Функция получает текущую глубину пикселей и по ней определяет, какую версию BMP-файла (палитровую или беспалитровую) следует использовать при восстановлении поверхности. Затем мы проверяем, действительно ли поверхность была потеряна.

    При этом учитывается, что «потеряться» могут лишь поверхности, находящиеся в видеопамяти. Поверхности в системной памяти никогда не теряются, поэтому предварительная проверка может избавить нас от хлопот с повторной загрузкой содержимого BMP-файла и, следовательно, ускорить процесс восстановления.

    Если поверхность потеряна, мы вызываем функцию Restore() интерфейса DirectDrawSurface, чтобы вернуть видеопамять в объеме, достаточном для хранения поверхности. Содержимое поверхности восстанавливается функцией LoadSurface().

    Восстановление двух других поверхностей происходит несколько иначе, потому что их содержимое не является BMP-файлом. Поверхность menusurf восстанавливается функцией Restore() с последующим вызовом функции UpdateMenuSurface(), в которой происходит стирание и перерисовка поверхности меню. При восстановлении fpssurf мы сначала вызываем Restore(), а потом стираем содержимое поверхности, потому что это содержимое после восстановления оказывается непредсказуемым. Не забывайте, поверхности чаще всего теряются из-за того, что занимаемая ими видеопамять понадобилась другому приложению.

    Обратите внимание: мы не восстанавливаем значение, отображаемое на поверхности FPS. Потеря первичной поверхности и вторичного буфера означает, что последнее вычисленное значение FPS, по всей видимости, стало неверным. Наша программа было свернута или уступила фокус другому приложению; в любом случае нельзя с уверенностью сказать, насколько быстро обновляется изображение в данный момент. Вместо того чтобы выводить устаревшее значение FPS, мы очищаем поверхность и присваиваем переменной displayfps значение FALSE, тем самым запрещая вывод поверхности до получения новой величины FPS. Кроме того, мы обнуляем переменную framecount, чтобы перезапустить механизм вычисления FPS.

    Вот и все, что касается программы Switch. Пора заняться частотой смены кадров и перейти к приложению SuperSwitch.

    Частота смены кадров 

    В главе 1 мы узнали, что DirectDraw позволяет приложению задать не только видеорежим, но и частоту смены кадров. Однако перед тем, как писать демонстрационную программу, следует разобраться, что же такое «частота смены кадров».

    Частота смены кадров представляет собой скорость обновления экрана. Например, частота в 60 Гц означает, что экран перерисовывается 60 раз в секунду. Данный показатель представляет большой интерес для программистов, работающих с DirectDraw, потому что он управляет скоростью работы приложения. Для полноэкранных приложений DirectDraw с возможностью переключения страниц это происходит автоматически, потому что функция Flip() интерфейса DirectDrawSurface синхронизирует операцию переключения страниц с частотой смены кадров. Такая синхронизация определяет (или, если хотите, ограничивает) скорость, с которой приложение обновляет экран.

    Возможно, кому-то это покажется досадной помехой — ведь речь идет о скорости работы приложения. Но, как я упоминал в главе 2, частота смены кадров выбирается с учетом возможностей нашего зрения. Если человеческий глаз способен воспринять именно такой объем графической информации, нет смысла выводить больше кадров (если, конечно, ваши приложения написаны для людей).

    И все же некоторые частоты смены кадров оставляют желать лучшего. Низкие частоты (особенно ниже 60 Гц) раздражают и обычно плохо подходят для высокопроизводительных приложений. С помощью DirectDraw API ваше приложение может узнать, какие частоты поддерживаются для конкретного видеорежима, и активизировать их по мере необходимости.

    Тем не менее подобные «игры» с частотами требуют определенной осторожности. Во-первых, не все видеоустройства позволяют управлять частотой смены кадров. В этом случае вам придется использовать параметры по умолчанию, какими бы они ни были. Кроме того, даже если некоторая частота поддерживается видеокартой, это еще не означает, что она поддерживается монитором. В Windows NT, где DirectDraw не проверяет тип монитора, можно легко включить видеорежим с частотой, при которой на экран выводится сплошной «мусор». Если ваше приложение управляет частотой смены кадров, позаботьтесь о том, чтобы пользователь всегда мог вернуть исходные параметры.

    Кроме аппаратных возможностей следует также учесть быстродействие самого приложения. Даже если вы установите видеорежим с частотой 100 Гц, это еще не значит, что приложение будет выводить 100 кадров в секунду. В какой-то момент оно отстанет от видеорежима, и тогда на каждое обновление экрана будет уходить два цикла. Другими словами, приложение будет пропускать циклы обновления, а FPS упадет до половины частоты смены кадров — для быстрых приложений такая потеря производительности оказывается заметной. По этой причине приложения, изменяющие частоту смены кадров, должны распознавать такие ситуации и выбирать более низкую частоту.

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

    Перед тем как заниматься программой SuperSwitch, я хочу снова напомнить, что не все видеокарты позволяют выбирать частоту смены кадров. Если программа SuperSwitch не обнаруживает такой возможности, меню частот будет содержать лишь одну строку — 0 Гц, где ноль обозначает частоту, принятую для данного видеорежима по умолчанию. 

    Программа SuperSwitch 

    Программа SuperSwitch, как и программа Switch, позволяет установить любой видеорежим, но, кроме того, для выбранного видеорежима можно задать и частоту смены кадров. Основной экран программы SuperSwitch выглядит так же, как и в Switch, но при выборе видеорежима появляется подменю с возможными частотами смены кадров (см. рис. 4.2).

    Перед тем как инициализировать DirectDraw, программа выводит диалоговое окно с кратким описанием. В этом окне можно отключить смену частоты, и тогда программа работает точно так же, как и программа Switch. 

    Класс SuperSwitchWin 

    Поскольку программа SuperSwitch является видоизмененной версией Switch, мы не будем обсуждать весь ее код. Вместо этого будут рассмотрены лишь отличающиеся фрагменты SuperSwitch.

    Рис. 4.2. Программа SuperSwitch


    Отличия начинаются с того, что классы в этой программе называются SuperSwitchWin и SuperSwitchApp (вместо SwitchWin и SwitchApp). Класс SuperSwitchWin похож на SwitchWin, но в нем имеется несколько новых функций и переменных. Давайте посмотрим, что же изменилось. Объявление класса SuperSwitchWin приведено в листинге 4.6.


    Листинг 4.6. Объявление класса SuperSwitchWin

    class SuperSwitchWin : public DirectDrawWin {

    public:

     SuperSwitchWin();

    protected:

     //{{AFX_MSG(SuperSwitchWin)

     afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);

     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

     //}}AFX_MSG

     DECLARE_MESSAGE_MAP()

    private:

     int SelectDriver();

     int SelectInitialDisplayMode();

     BOOL CreateCustomSurfaces();

     static HRESULT WINAPI StoreModeInfo(LPDDSURFACEDESC, LPVOID);

     void DrawScene();

     void RestoreSurfaces();

     BOOL CreateModeMenuSurface();

     BOOL UpdateModeMenuSurface();

     BOOL CreateRateMenuSurface();

     BOOL UpdateRateMenuSurface();

     BOOL CreateFPSSurface();

     BOOL UpdateFPSSurface();

    private:

     LPDIRECTDRAWSURFACE bmpsurf;

     int x,y;

     int xinc, yinc;

     LPDIRECTDRAWSURFACE modemenusurf;

     int selectmode;

     LPDIRECTDRAWSURFACE ratemenusurf;

     int selectrate;

     int numrates;

     BOOL ratemenu_up;

     LPDIRECTDRAWSURFACE fpssurf;

     RECT fpsrect;

     BOOL displayfps;

     DWORD framecount;

     BOOL include_refresh;

     CArray<DWORD,DWORD> refresh_rates[MAXDISPLAYMODES];

     HFONT smallfont, largefont;

    };

    Отличия начинаются с функции OnCreate(). Мы переопределяем функцию DirectDrawWin::OnCreate() так, чтобы перед инициализацией DirectDraw в ней выводилось диалоговое окно (в котором можно отключить изменение частоты смены кадров).

    Другая новая функция — StoreModeInfo(). Эта функция косвенного вызова вызывается при составлении списка частот каждого видеорежима. Как говорилось в главе 3, класс DirectDrawWin имеет для этой цели собственную функцию косвенного вызова (DisplayModeAvailable()). Вместо того чтобы изменять класс DirectDrawWin, мы воспользуемся функцией StoreModeInfo(), приспособленной для целей конкретного приложения. Это означает, что список видеорежимов будет составляться дважды: сначала без частот смены кадров (класс DirectDrawWin), а потом с частотами (класс SuperSwitchWin).

    Далее в списке идут четыре новые функции:

    • CreateModeMenuSurface()

    • UpdateModeMenuSurface()

    • CreateRateMenuSurface()

    • UpdateRateMenuSurface()

    Функции CreateModeMenuSurface() и UpdateModeMenuSurface() — это просто переименованные функции CreateMenuSurface() и UpdateMenuSurface() из программы Switch. Их пришлось переименовать, потому что теперь существуют две поверхности меню: одна — для видеорежимов, а другая — для частот смены кадров. Функции CreateModeMenuSurface() и UpdateModeMenuSurface() работают с поверхностью меню видеорежимов. Две новые функции, CreateRateMenuSurface() и UpdateRateMenuSurface(), предназначены для работы с поверхностью меню частот.

    Теперь давайте рассмотрим новые и изменившиеся переменные класса. Указатель menusurf из программы Switch был переименован в modemenusurf по той же причине, по которой были переименованы функции для работы с поверхностью меню видеорежимов. Далее в классе появились шесть новых переменных. Я снова приведу объявления новых переменных класса из листинга 4.6:

    LPDIRECTDRAWSURFACE ratemenusurf;

    int selectrate;

    int numrates;

    BOOL ratemenu_up;

    BOOL include_refresh;

    CArray<DWORD,DWORD> refresh_rates[MAXDISPLAYMODES];

    Переменная ratemenusurf представляет собой указатель на интерфейс DirectDrawSurface и используется для работы с поверхностью меню частот. В целых переменных selectrate и numrates хранятся соответственно текущая выделенная частота и общее количество отображаемых частот. Логическая переменная ratemenu_up показывает, отображается ли меню частот в данный момент.

    Значение логической переменной include_refresh определяется выбором пользователя, сделанным в окне диалога при старте программы. Если эта переменная равна TRUE, программа создает и выводит меню со списком частот для каждого выделенного видеорежима. Если переменная равна FALSE, частоты не отображаются. Наконец, массив refresh_rates предназначен для хранения возможных частот каждого видеорежима. Содержимое массива определяется с помощью косвенно вызываемой функции StoreModeInfo() и используется функцией UpdateRateMenusurface()

    Инициализация приложения 

    Как упоминалось выше, перед инициализацией DirectDraw программа SuperSwitch выводит в функции SuperSwitchWin::OnCreate() диалоговое окно. После вывода диалогового окна функция вызывает версию OnCreate() класса DirectDrawWin. Код функции SuperSwitchWin::OnCreate() выглядит так:

    int SuperSwitchWin::OnCreate(LPCREATESTRUCT lpCreateStruct) {

     IntroDialog introdialog;

     if (introdialog.DoModal()!=IDOK) return -1;

     include_refresh=introdialog.include_refresh;

     if (DirectDrawWin::OnCreate(lpCreateStruct)==-1) return -1;

     if (include_refresh) ddraw2->EnumDisplayModes(DDEDM_REFRESHRATES, 0, this, StoreModeInfo);

     return 0;

    }

    Сначала мы создаем объект класса IntroDialog — этот класс-оболочка был сгенерирован ClassWizard. Диалоговое окно отображается функцией CDialog::DoModal(), которая возвращает код IDOK в случае нажатия пользователем кнопки OK. Если пользователь закрывает диалоговое окно другим способом (например, нажимая кнопку Cancel), функция OnCreate() возвращает код –1, что для MFC является признаком завершения приложения. Если была нажата кнопка OK, переменной include_refresh присваивается значение в зависимости от состояния флажка в диалоговом окне.

    Теперь мы вызываем версию OnCreate() класса DirectDrawWin, где и происходит инициализация DirectDraw. Функция составляет список видеорежимов, активизирует исходный режим и создает поверхности приложения. Если вызов функции OnCreate() завершается неудачей, мы завершаем приложение, возвращая код –1.

    Следующий шаг — повторное составление списка видеорежимов. На этот раз при вызове функции EnumDisplayModes() в первом аргументе передается флаг DDEDM_REFRESHRATES, согласно которому каждый видеорежим должен быть включен в список по одному разу для каждой поддерживаемой частоты. В результате мы сможем построить список частот для каждого видеорежима. Четвертый аргумент EnumDisplayModes() — функция косвенного вызова StoreModeInfo(), которая выглядит так:

    HRESULT WINAPI SuperSwitchWin::StoreModeInfo(LPDDSURFACEDESC desc, LPVOID p) {

     DWORD w=desc->dwWidth;

     DWORD h=desc->dwHeight;

     DWORD d=desc->ddpfPixelFormat.dwRGBBitCount;

     DWORD r=desc->dwRefreshRate;

     SuperSwitchWin* win=(SuperSwitchWin*)p;

     int index=win->GetDisplayModeIndex(w, h, d);

     win->refresh_rates[index].Add(r);

     return DDENUMRET_OK;

    }

    Функции StoreModeInfo()> передается указатель на структуру DDSURFACEDESC с описанием очередного видеорежима. В описание входит частота смены кадров (поле dwRefreshRate), а также размеры, по которым определяется индекс режима. Затем этот индекс используется для сохранения частоты видеорежима в массиве.

    После выхода из функции OnCreate() класс DirectDrawWin вызывает функцию CreateCustomSurfaces(). По сравнению с программой Switch эта функция не изменилась; она по-прежнему создает три поверхности, потому что новая поверхность (ratemenusurface) создается только в случае необходимости. 

    Графический вывод 

    Давайте посмотрим, как в программе SuperSwitch реализована функция DrawScene(). Она похожа на одноименную функцию из программы Switch, за исключением того, что при выборе видеорежима новая версия должна отображать поверхность со списком частот. Функция DrawScene() выглядит так:

    void SuperSwitchWin::DrawScene() {

     ClearSurface(backsurf, 0);

     BltSurface(backsurf, bmpsurf, x, y);

     x+=xinc; y+=yinc;

     const CRect& displayrect=GetDisplayRect();

     if (x<-160 || x>displayrect.right-160) {

      xinc=-xinc;

      x+=xinc;

     }

     if (y<-100 || y>displayrect.bottom-100) {

      yinc=-yinc;

      y+=yinc;

     }

     backsurf->BltFast(0, 0, modemenusurf, 0, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

     if (ratemenu_up) {

      DWORD w,h;

      GetSurfaceDimensions(ratemenusurf, w, h);

      backsurf->BltFast((320-w)/2, (200-h)/2, ratemenusurf, 0, DDBLTFAST_WAIT);

     }

     UpdateFPSSurface();

     if (displayfps) {

      int x=displayrect.right-fpsrect.right;

      int y=displayrect.bottom-fpsrect.bottom;

      backsurf->BltFast(x, y, fpssurf, &fpsrect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

     }

     primsurf->Flip(0, DDFLIP_WAIT);

    }

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

    Обработка пользовательского ввода 

    Теперь в программу необходимо включить код для обработки пользовательского ввода при работе с меню частот. Мы воспользуемся функцией OnKeyDown() (листинг 4.7).


    Листинг 4.7. Функция SuperSwitch::OnKeyDown()

    void SuperSwitchWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {

     int newindex;

     int nmodes=GetNumDisplayModes();

     if (nmodes>maxmodes) nmodes=maxmodes;

     int rows=nmodes/menucols;

     if (nmodes%menucols) rows++;

     switch (nChar) {

     case VK_ESCAPE:

      if (!include_refresh || !ratemenu_up) {

       PostMessage(WM_CLOSE);

       break;

      }

      if (ratemenu_up) {

       ratemenu_up=FALSE;

       if (ratemenusurf)     ratemenusurf->Release(), ratemenusurf=0;

      }

      break;

     case VK_UP:

      if (include_refresh && ratemenu_up) {

       if (selectrate>0) {

        selectrate--;

        UpdateRateMenuSurface();

       }

      } else {

       newindex=selectmode-1;

       if (newindex>=0) {

        selectmode=newindex;

        UpdateModeMenuSurface();

       }

      }

      break;

     case VK_DOWN:

      if (include_refresh && ratemenu_up)  {

       if (selectrate<numrates-1) {

        selectrate++;

        UpdateRateMenuSurface();

       }

      } else {

       newindex=selectmode+1;

       if (newindex>nmodes) {

        selectmode=newindex;

        UpdateModeMenuSurface();

       }

      }

      break;

     case VK_LEFT:

      if (include_refresh && ratemenu_up)   break;

      newindex=selectmode-rows;

      if (newindex>=0) {

       selectmode=newindex;

       UpdateModeMenuSurface();

      }

      break;

     case VK_RIGHT:

      if (include_refresh && ratemenu_up)    break;

      newindex=selectmode+rows;

      if (newindex<nmodes) {

       selectmode=newindex;

       UpdateModeMenuSurface();

      }

      break;

     case VK_RETURN:

      if (include_refresh) {

       if (ratemenu_up) {

        int rate=refresh_rates[selectmode][selectrate];

        ActivateDisplayMode(selectmode, rate);

        x=y=0;

        ratemenu_up=FALSE;

       } else {

        CreateRateMenuSurface();

        UpdateRateMenuSurface();

        ratemenu_up=TRUE;

       }

      } else {

       if (selectmode!=GetCurDisplayMode()) {

        ActivateDisplayMode(selectmode);

        x=y=0;

       }

      }

      break;

     case 'S':

      SaveSurface(primsurf, "SuperSwitch.bmp");

      break;

     default:

      DirectDrawWin::OnKeyDown(nChar, nRepCnt, nFlags);

     }

    }

    Все case-секции оператора switch были изменены для работы с новым меню. При нажатии клавиши Escape программа по-прежнему завершает работу, если меню частот в данный момент не отображается; тем не менее, если меню присутствует на экране, клавиша Escape просто скрывает его. Действие клавиш со стрелками также зависит от состояния меню. Если меню частот отображается, стрелки ­ и изменяют выделенную частоту, а если нет — выделенный пункт в меню видеорежимов.

    Самые существенные различия связаны с обработкой клавиши Enter. Если во время нажатия клавиши Enter меню частот не отображается, мы вызываем функции CreateRateMenuSurface() и UpdateRateMenuSurface() и присваиваем флагу ratemenu_up значение TRUE. Давайте рассмотрим эти две функции. Функция CreateRateMenuSurface() выглядит так:

    BOOL SuperSwitchWin::CreateRateMenuSurface() {

     if (ratemenusurf) ratemenusurf->Release(), ratemenusurf=0;

     int rates=refresh_rates[selectmode].GetSize();

     ratemenusurf=CreateSurface(80, rates*12+22);

     return TRUE;

    }

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

    Функция UpdateRateMenuSurface() отвечает за отображение текста меню. Выглядит она так:

    BOOL SuperSwitchWin::UpdateRateMenuSurface() {

     RECT rect;

     GetSurfaceRect(ratemenusurf, rect);

     rect.left++;

     rect.top++;

     rect.right--;

     rect.bottom--;

     if (!ClearSurface(ratemenusurf, 0, 200, 132)) TRACE("first Clear failed\n");

     if (!ClearSurface(ratemenusurf, 0, 128, 100, &rect)) TRACE("second Clear failed\n");

     HDC hdc;

     ratemenusurf->GetDC(&hdc);

     SelectObject(hdc, smallfont);

     SetBkMode(hdc, TRANSPARENT);

     SetTextColor(hdc, ratetextshadow);

     ExtTextOut(hdc, 6, 4, 0, 0, rateheader, strlen(rateheader), 0);

     SetTextColor(hdc, ratetextcolor);

     ExtTextOut(hdc, 5, 3, 0, 0, rateheader, strlen(rateheader), 0);

     CArray<DWORD,DWORD>& ratelist=refresh_rates[selectmode];

     numrates=ratelist.GetSize();

     for (int i=0; i<numrates; i++) {

      char buf[10];

      int len=sprintf(buf, "%d hz", ratelist[i]);

      SetTextColor(hdc, ratetextshadow);

      ExtTextOut(hdc, 11, i*12+18, 0, 0, buf, len, 0);

      if (i==selectrate) SetTextColor(hdc, ratehighlightcolor);

      else SetTextColor(hdc, ratetextcolor);

      ExtTextOut(hdc, 10, i*12+17, 0, 0, buf, len, 0);

     }

     ratemenusurf->ReleaseDC(hdc);

     return TRUE;

    }

    Прежде всего функция очищает поверхность, вызывая ClearSurface(). Затем содержимое массива refresh_rates используется для вывода текстовых строк, связанных с каждым пунктом меню. Вывод текста, как обычно, осуществляется функцией GetDC() интерфейса DirectDrawSurface в сочетании с текстовыми функциями Win32. Перед выходом из функции UpdateRateMenuSurface() контекст устройства, полученный функцией GetDC(), освобождается с помощью функции ReleaseDC()

    Заключение

    В этой главе мы рассмотрели две демонстрационные программы и воспользовались такими возможностями DirectDraw, как переключение видеорежимов и частот смены кадров, а также применили цветовые ключи. Для переключения видеорежимов и частот использовалась функция EnumDisplayModes() интерфейса DirectDraw в сочетании с функцией SetDisplayMode(), а для работы с цветовыми ключами — функции SetColorKey() и BltFast() интерфейса DirectDrawSurface. Вывод текста в программах осуществлялся с помощью функции GetDC() интерфейса DirectDrawSurface и текстовых функций Win32.

    В главе 5 мы научимся работать с поверхностями на уровне отдельных битов, что позволит установить максимальный контроль над содержимым палитровых и беспалитровых поверхностей. Затем полученные знания будут использованы для написания программы просмотра BMP-файлов.







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх