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

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

Приложение Bounce прошло стадию инициализации, и теперь все готово к графическому выводу. Однако сначала мы посмотрим, как в классах DirectDrawWin и DirectDrawApp организуется обновление кадров.

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

BOOL DirectDrawApp::OnIdle(LONG) {
 if (ddwin->PreDrawScene()) ddwin->DrawScene();
 return TRUE;
}

Функция OnIdle() вызывает функцию DirectDrawWin::PreDrawScene() и в зависимости от полученного результата вызывает функцию DrawScene(). Функция OnIdle() всегда возвращает TRUE, потому что при возврате FALSE MFC перестает ее вызывать. Функция PreDrawScene() реализована так:

BOOL DirectDrawWin::PreDrawScene() {
 if (window_active && primsurf->IsLost()) {
  HRESULT r;
  r=primsurf->Restore();
  if (r!=DD_OK) TRACE("can't restore primsurfn");
  r=backsurf->Restore();
  if (r!=DD_OK) TRACE("can't restore backsurfn");
  RestoreSurfaces();
 }
 return window_active;
}

Функция PreDrawScene() выполняет сразу две задачи. Во-первых, она следит за тем, чтобы для неактивного приложения не выполнялись попытки обновить изображение на экране. Во-вторых, она восстанавливает поверхности приложения в случае их потери.

Потеря поверхностей происходит из-за того, что DirectDraw выделяет занятую видеопамять для других целей. Потерянную поверхность можно легко восстановить, но лишь после того, как приложение станет активным, поэтому перед тем, как восстанавливать поверхности, функция PreDrawScene() ждет установки флага window_active (состояние флага window_active зависит от сообщений WM_ACTIVATEAPP, обрабатываемых функцией DirectDrawWin::OnActivateApp). После восстановления первичной поверхности и вторичного буфера вызывается функция RestoreSurfaces(). Она является чисто виртуальной функцией, которая должна быть реализована в производных классах. Сейчас мы рассмотрим ее возможную реализацию.

Так как функция OnIdle() вызывает DrawScene() лишь после проверки результата PreDrawScene(), DrawScene() будет вызвана лишь в том случае, если приложение активно, а первичная и вторичная поверхности не были потеряны.

Функция DrawScene()

Классы, производные от DirectDrawWin, реализуют функцию DrawScene(), в которой происходит обновление экрана. Версия DrawScene() из класса BounceWin выглядит так:

void BounceWin::DrawScene() {
 CRect limitrect=GetDisplayRect();
 x+=xinc;
 y+=yinc;
 if (x<-160 || x>limitrect.right-160) {
  xinc=-xinc;
  x+=xinc;
 }
 if (y<-100 || y>limitrect.bottom-120) {
  yinc=-yinc;
  y+=yinc;
 }
 ClearSurface(backsurf, 0);
 BltSurface(backsurf, surf1, x, y);
 primsurf->Flip(0, DDFLIP_WAIT);
}

Сначала функция GetDisplayRect() получает объект CRect, хранящий ширину и высоту текущего видеорежима. Эти размеры будут использоваться для ограничения перемещений растрового изображения в соответствии с видеорежимом. Далее вычисляются значения переменных x и y класса BounceWin, определяющих местонахождение растра на экране.

Затем мы вызываем функцию ClearSurface() и передаем ей два аргумента: указатель backsurf и 0. Это приводит к тому, что вторичный буфер заполняется черным цветом. Хотя я упоминал о том, что использование ClearSurface() иногда осложняется различными форматами пикселей, заполнение поверхностей черным работает надежно. Для палитровых поверхностей 0 означает черный цвет, потому что по умолчанию он стоит в палитре на первом месте; для беспалитровых поверхностей 0 всегда соответствует черному цвету.

Функция DrawScene() использует функцию DirectDrawWin::BltSurface() для копирования поверхности surf1 на поверхность backsurf. Два последних аргумента BltSurface() определяют точку поверхности-приемника, куда должно быть скопировано содержимое источника. Для выполнения этой операции можно было бы воспользоваться функцией Blt() или BltFast() интерфейса DirectDrawSurface, но мы не делаем этого из-за возможного отсечения. Обратите внимание - код, определяющий положение растра, позволяет источнику выйти за пределы приемника, в результате чего может потребоваться отсечение. Мы не можем воспользоваться функцией Blt(), потому что тогда потребовалось бы присоединить к приемнику объект DirectDrawClipper, чего мы не делаем. Функция BltFast() тоже не подходит, потому что она вообще не поддерживает отсечения. Функция BltSurface() автоматически выполняет отсечение, а функции Blt() и BltFast() вызываются внутри нее.

Но перед тем, как переходить к функции BltSurface(), мы закончим рассмотрение функции DrawScene(). Она завершается вызовом функции Flip(). При этом происходит переключение страниц, и подготовленный нами кадр отображается на экране. Функция Flip() получает два аргумента: указатель на поверхность и переменную типа DWORD, предназначенную для установки флагов. Указатель на поверхность необходим лишь в нестандартных ситуациях, когда в переключении поверхностей участвует несколько вторичных буферов. Второй аргумент обычно содержит флаг DDFLIP_WAIT, показывающий, что возврат из функции должен происходить только после того, как переключение страниц завершится.

Функция BltSurface()

Функция BltSurface() класса DirectDrawWin оказывается более гибкой и удобной по сравнению с функциями DirectDrawSurface::Blt() и BltFast(). Мы уже видели, как BltSurface() используется внутри функции BounceWin::DrawScene(), а сейчас рассмотрим саму функцию.

Функция BltSurface() требует передачи четырех аргументов, а пятый аргумент необязателен. Первые два аргумента представляют собой указатели на поверхности — источник и приемник. Следующие два аргумента — координаты x и y, определяющие положение копируемой области на приемнике. По умолчанию блиттинг выполняется без цветовых ключей, однако их можно активизировать с помощью необязательного пятого параметра. Код функции BltSurface() приведен в листинге 3.3.

Листинг 3.3. Функция BltSurface()

BOOL DirectDrawWin::BltSurface(LPDIRECTDRAWSURFACE destsurf, LPDIRECTDRAWSURFACE srcsurf, int x, int y, BOOL srccolorkey) {
 if (destsurf==0 || srcsurf==0) return FALSE;
 BOOL use_fastblt=TRUE;
 DDSURFACEDESC destsurfdesc;
 ZeroMemory(&destsurfdesc, sizeof(destsurfdesc));
 destsurfdesc.dwSize = sizeof(destsurfdesc);
 destsurf->GetSurfaceDesc(&destsurfdesc);
 CRect destrect;
 destrect.left=0;
 destrect.top=0;
 destrect.right=destsurfdesc.dwWidth;
 destrect.bottom=destsurfdesc.dwHeight;
 DDSURFACEDESC srcsurfdesc;
 ZeroMemory(&srcsurfdesc, sizeof(srcsurfdesc));
 srcsurfdesc.dwSize = sizeof(srcsurfdesc);
 srcsurf->GetSurfaceDesc(&srcsurfdesc);
 CRect srcrect;
 srcrect.left=0;
 srcrect.top=0;
 srcrect.right=srcsurfdesc.dwWidth;
 srcrect.bottom=srcsurfdesc.dwHeight;
 // Проверить, нужно ли что-нибудь делать...
 if (x+srcrect.left>=destrect.right) return FALSE;
 if (y+srcrect.top>=destrect.bottom) return FALSE;
 if (x+srcrect.right<=destrect.left) return FALSE;
 if (y+srcrect.bottom<=destrect.top) return FALSE;
 // При необходимости выполнить отсечение
 // для прямоугольной области источника
 if (x+srcrect.right>destrect.right) srcrect.right-=x+srcrect.right-destrect.right;
 if (y+srcrect.bottom>destrect.bottom) srcrect.bottom-=y+srcrect.bottom-destrect.bottom;
 CRect dr;
 if (x<0) {
  srcrect.left=-x;
  x=0;
  dr.left=x;
  dr.top=y;
  dr.right=x+srcrect.Width();
  dr.bottom=y+srcrect.Height();
  use_fastblt=FALSE;
 }
 if (y<0) {
  srcrect.top=-y;
  y=0;
  dr.left=x;
  dr.top=y;
  dr.right=x+srcrect.Width();
  dr.bottom=y+srcrect.Height();
  use_fastblt=FALSE;
 }
 DWORD flags;
 if (use_fastblt) {
  flags=DDBLTFAST_WAIT;
  if (srccolorkey) flags |= DDBLTFAST_SRCCOLORKEY;
  destsurf->BltFast(x, y, srcsurf, &srcrect, flags);
 } else {
  flags=DDBLT_WAIT;
  if (srccolorkey) flags |= DDBLT_KEYSRC;
  destsurf->Blt(&dr, srcsurf, &srcrect, flags, 0);
 }
 return TRUE;
}

Сначала функция BltSurface() проверяет указатели на поверхности. Если хотя бы один из них равен нулю, функция возвращает FALSE, тем самым сообщая о неудаче. Если проверка прошла успешно, два объекта CRect инициализируются в соответствии с размерами поверхностей, полученными с помощью функции DirectDrawSurface::GetSurfaceDesc().

Затем BltSurface() проверяет, что попадает ли точка назначения в границы приемника. Если координаты x и y таковы, что копия не пересекается с поверхностью приемника, блиттинг не нужен, поэтому мы просто выходим из функции.

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

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

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

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

void BounceWin::RestoreSurfaces() {
 if (surf1->IsLost()==FALSE) return;
 CString filename;
 if (GetCurDisplayDepth()==8) filename="tri08.bmp";
 else filename="tri24.bmp";
 surf1->Restore();
 LoadSurface(surf1, filename);
}

DirectDraw может отнимать у неактивного приложения только поверхности, находящиеся в видеопамяти, так что нет смысла в восстановлении поверхностей из системной памяти. Поэтому RestoreSurfaces() сначала проверяет, была ли потеряна единственная вспомогательная поверхность нашего приложения, и если нет — функция прекращает работу. Если же поверхность была потеряна, мы восстанавливаем ее память функцией Restore(), а содержимое — функцией LoadSurface().

Завершение

Как бы ни была хороша программа Bounce, рано или поздно вам захочется убрать ее с экрана. Нажатие клавиши Escape завершает работу программы. Это происходит в обработчике OnKeyDown():

void bounceWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
 if (nChar==VK_ESCAPE) PostMessage(WM_CLOSE);
 DirectDrawWin::OnKeyDown(nChar, nRepCnt, nFlags);
}

Приложение завершает работу, отправляя сообщение WM_CLOSE. В нашем приложении на это сообщение реагирует и класс окна, и класс приложения. Класс окна отвечает сообщением WM_DESTROY, для которого в DirectDrawWin предусмотрен обработчик OnDestroy(). Класс DirectDrawWin в данном обработчике освобождает объекты DirectDraw и всю память, занимаемую приложением. Функция OnDestroy() выглядит так:

void DirectDrawWin::OnDestroy() {
 if (primsurf) primsurf->Release(), primsurf=0;
  if (palette) palette->Release(), palette=0;
  if (ddraw2) ddraw2->Release(), ddraw2=0;
  for (int i=0;i<totaldrivers;i++) {
   if (driver[i].guid) delete[] driver[i].guid;
   free(driver[i].desc);
   free(driver[i].name);
  }
 }
}

Каждый из указателей на интерфейсы DirectDraw сначала освобождается, а затем обнуляется. Затем мы освобождаем память, занятую информацией о драйверах DirectDraw.

Класс приложения обрабатывает завершение в функции ExitInstance(), в которой удаляется класс окна:

int DirectDrawApp::ExitInstance() {
 delete ddwin;
 return CWinApp::ExitInstance();
}

На этом наше знакомство с программой Bounce заканчивается. Однако до сих пор речь шла только о полноэкранных приложениях. Оставшаяся часть этой главы посвящена оконным приложениям.

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

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

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