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

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

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

В главе 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 failedn");
 if (!ClearSurface(ratemenusurf, 0, 128, 100, &rect)) TRACE("second Clear failedn");
 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-файлов.

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


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