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

Оконные приложения

Оконные приложения

Наверное, вы уже поняли, что полноэкранным приложениям в этой книге уделяется особое внимание. Все программы на CD-ROM работают в полноэкранном режиме, и в этой главе до настоящего момента все внимание было сосредоточено исключительно на полноэкранных приложениях.

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

В начале этой главы мы воспользовались DirectDraw AppWizard и создали приложение Bounce. При этом мы указали, что создаваемая программа должна быть полноэкранной. Чтобы получить рассматриваемый ниже код, следует снова запустить AppWizard и выбрать оконное приложение.

Структура приложения

По своей структуре оконная версия приложения Bounce почти не отличается от полноэкранной. Как и прежде, классы DirectDrawWin и DirectDrawApp организуют поддержку DirectDraw и используются в качестве базовых для классов, относящихся к конкретным приложениям.

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

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

int DirectDrawWin::OnCreate(LPCREATESTRUCT) {
 LPDIRECTDRAW ddraw1;
 DirectDrawCreate(0, &ddraw1, 0);
 ddraw1->QueryInterface(IID_IDirectDraw2, (void**)&ddraw2);  ddraw1->Release(), ddraw1=0;  ddraw2->SetCooperativeLevel(GetSafeHwnd(), DDSCL_NORMAL);
 DetectDisplayMode();
 if (CreateFlippingSurfaces()==FALSE) {
  AfxMessageBox("CreateFlippingSurfaces() failed");
  return FALSE;
 }
 if (CreateCustomSurfaces()==FALSE) {
  AfxMessageBox("CreateCustomSurfaces() failed");
  return FALSE;
 }
 return 0;
}

Сначала указатель на интерфейс DirectDraw(ddraw1) инициализируется функцией DirectDrawCreate(). Указатель ddraw1, как и в полноэкранной версии, используется только для получения указателя на интерфейс DirectDraw2, после чего освобождается.

Затем функция OnCreate() вызывает функцию SetCooperativeLevel(). В полноэкранном приложении уровень кооперации определялся тремя флагами: DDSCL_EXCLUSIVE, DDSCL_FULLSCREEN и DDSCL_ALLOWMODEX. В данном случае используется только флаг DDSCL_NORMAL.

Функция DetectDisplayMode() инициализирует некоторые переменные класса DirectDrawWin. Она выглядит так:

BOOL DirectDrawWin::DetectDisplayMode() {
 DDSURFACEDESC desc;
 ZeroMemory(&desc, sizeof(desc));
 desc.dwSize=sizeof(desc);
 if (ddraw2->GetDisplayMode(&desc)!=DD_OK) {
  TRACE("GetDisplayMode() failedn");
  return FALSE;
 }
 displayrect.left=0;
 displayrect.top=0;
 displayrect.right=desc.dwWidth;
 displayrect.bottom=desc.dwHeight;
 displaydepth=desc.ddpfPixelFormat.dwRGBBitCount;
 return TRUE;
}

Функция DetectDisplayMode() с помощью функции GetDisplayMode() интерфейса DirectDraw получает информацию о текущем видеорежиме Windows. Говоря точнее, разрешение экрана и глубина пикселей текущего видеорежима сохраняются в переменных displayrect и displaydepth.

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

Листинг 3.4. Функция CreateFlippingSurfaces() в оконном приложении

BOOL DirectDrawWin::CreateFlippingSurfaces() {
 HRESULT r;
 DDSURFACEDESC desc;
 desc.dwSize = sizeof(desc);
 desc.dwFlags = DDSD_CAPS;
 desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
 r=ddraw2->CreateSurface(&desc, &primsurf, 0);
 if (r!=DD_OK) {
  TRACE("FAILED to create 'primsurf'n");
  return FALSE;
 }
 r=ddraw2->CreateClipper(0, &clipper, 0);
 if (r!=DD_OK) {
  TRACE("CreateClipper() failedn");
  return FALSE;
 }
 r=clipper->SetHWnd(0, GetSafeHwnd());
 if (r!=DD_OK) {
  TRACE("SetHWnd() failedn");
  return FALSE;
 }
 r=primsurf->SetClipper(clipper);
 if (r!=DD_OK) {
  TRACE("SetClipper() failedn");
  return FALSE;
 }
 ZeroMemory(&desc, sizeof(desc));
 desc.dwSize = sizeof(desc);
 desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
 desc.dwWidth = displayrect.Width();
 desc.dwHeight = displayrect.Height();
 desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
 r=ddraw2->CreateSurface(&desc, &backsurf, 0);
 if (r!=DD_OK) {
  TRACE("failed to create 'backsurf' in videon");
  videobacksurf=FALSE;
 } else {
  TRACE("Created backsurf in videon");
  videobacksurf=TRUE;
 }
 return TRUE;
}

Сначала мы создаем первичную поверхность. В полноэкранном варианте код выглядит по-другому, потому что здесь создается обычная, несоставная первичная поверхность. В структуре DDSURFACEDESC мы описываем первичную поверхность, используя только флаг DDSCAPS_PRIMARYSURFACE. Затем описанная поверхность создается функцией CreateSurface() интерфейса DirectDraw.

Далее функция CreateClipper() интерфейса DirectDraw создает объект отсечения. CreateClipper() получает три аргумента, однако первый и последний из них чаще всего равны нулю. Второй аргумент представляет собой адрес указателя на интерфейс DirectDrawClipper. В нашем случае используется переменная класса DirectDrawWin с именем clipper.

Объект отсечения нужен для ограничения вывода в программе. Поскольку наше приложение работает в окне, которое находится на рабочем столе вместе с другими окнами, при обновлении изображения необходимо учитывать присутствие этих окон. Чтобы объект отсечения автоматически выполнял свою работу, его необходимо присоединить к окну функцией SetHWnd() интерфейса DirectDrawClipper. Функция SetHWnd() получает два аргумента — двойное слово (DWORD), которое зарезервировано для будущего использования и пока должно быть равно нулю, и логический номер окна приложения.

Далее объект отсечения присоединяется к первичной поверхности приложения функцией SetClipper() интерфейса DirectDrawSurface. После такого присоединения можно осуществлять блиттинг на первичную поверхность с помощью функции Blt() интерфейса DirectDrawSurface. Использовать функцию BltFast() нельзя, потому что она не поддерживает отсечения.

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

Функция CreateFlippingSurfaces() пытается создать «идеальный» вторичный буфер, для чего используются флаг DDSCAPS_VIDEOMEMORY и функция CreateSurface(). Если вызов заканчивается успешно, флаг videobacksurf получает значение TRUE, а функция завершает работу. В противном случае вторичный буфер не создается, а флагу videobacksurf присваивается значение FALSE.

В том варианте вторичный буфер создается приложением в системной памяти позднее, в обработчике OnSize(). Функция OnSize() вызывается при изменении размеров окна приложения. Создавая вторичный буфер по размерам клиентской области окна, мы экономим память. Функция OnSize() выглядит так:

void DirectDrawWin::OnSize(UINT nType, int cx, int cy) {
 CWnd::OnSize(nType, cx, cy);
 CFrameWnd::GetClientRect(&clientrect);
 CFrameWnd::ClientToScreen(&clientrect);
 if (videobacksurf) return;
 DDSURFACEDESC desc;
 ZeroMemory(&desc, sizeof(desc));
 desc.dwSize = sizeof(desc);
 desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
 desc.dwWidth = clientrect.Width();
 desc.dwHeight = clientrect.Height();
 desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
 if (backsurf) backsurf->Release(), backsurf=0;
 HRESULT r=ddraw2->CreateSurface(&desc, &backsurf, 0);
 if (r!=DD_OK)  {
  TRACE("failed to create 'backsurf'n");
  return;
 } else TRACE("backsurf w=%d h=%dn", clientrect.Width(), clientrect.Height());
}

Инициализация приложения завершается вызовом функций StorePixelFormatData() и CreateCustomSurfaces(), происходящим в обработчике OnCreate(). Обе функции ведут себя точно так же, как и в полноэкранном приложении.

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

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

void BounceWin::DrawScene() {
 ClearSurface(backsurf, 0);
 CRect client=GetClientRect();
 int width=client.Width();
 int height=client.Height();
 x+=xinc;
 y+=yinc;
 if (x<-160 || x>width-160) {
  xinc=-xinc;
  x+=xinc;
 }
 if (y<-100 || y>height-100) {
  yinc=-yinc;
  y+=yinc;
 }
 BltSurface(backsurf, surf1, x, y);
 int offsetx=client.left;
 int offsety=client.top;
 RECT srect;
 srect.left=0;
 srect.top=0;
 srect.right=client.Width();
 srect.bottom=client.Height();
 RECT drect;
 drect.left=offsetx;
 drect.top=offsety;
 drect.right=offsetx+client.Width();
 drect.bottom=offsety+client.Height();
 primsurf->Blt(&drect, backsurf, &srect, DDBLT_WAIT, 0);
}

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

Вторая блит-операция копирует содержимое вторичного буфера на первичную поверхность. На этот раз используется функция Blt(), поскольку к первичной поверхности присоединен объект отсечения. Структуры srect и drect типа RECT определяют области источника и приемника, участвующие в блиттинге. Заметьте, что при вычислении области приемника используются переменные offsetx и offsety, в которых хранятся координаты клиентской области окна. Если убрать эти смещения из структуры drect, программа всегда будет выводить изображение в левом верхнем углу экрана независимо от расположения окна.

Заключение

В этой главе мы изучили почти весь код, сгенерированный AppWizard. Рассмотренное нами базовое приложение нетрудно изменить, поэтому попробуйте немного поэкспериментировать. Например, попытайтесь добавить в программу Bounce дополнительные поверхности или замените вызовы BltSurface() на BltFast() и посмотрите, что получится.

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

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

Оглавление статьи/книги

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