Графика для 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 primsurf\n"); r=backsurf->Restore(); if (r!=DD_OK) TRACE("can't restore backsurf\n"); RestoreSurfaces(); } return window_active; }

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

Как и в полноэкранном варианте, для обновления экрана класс 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, программа всегда будет выводить изображение в левом верхнем углу экрана независимо от расположения окна.




Графическим выводом в программе 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 ); }

Черный цвет не гарантирован
По умолчанию DirectDraw резервирует два элемента палитры: для черного (индекс 0) и для белого (индекс 255). Поэтому обычно заполнение поверхности нулями равносильно ее заливке черным цветом. Тем не менее в палитрах, созданных с флагом DDSCAPS_ALLOW256, можно задавать все 256 элементов.
Функция DirectDrawWin::CreateSurface() при создании и установке палитры (когда необязательный аргумент use_palette равен TRUE) использует флаг DDSCAPS_ALLOW256, поэтому первый элемент в палитрах наших приложений может быть отличен от черного цвета. Флаг можно удалить, но это нарушит цветопередачу при отображении BMP-файлов, у которых первый и последний элементы палитры отличны от черного и белого цветов соответственно.
В программах этой книги используются BMP-файлы, для которых положение черного и белого цвета в палитре совпадает с принятым в DirectDraw по умолчанию. В этом случае растры будут правильно отображаться независимо от флага DDSCAPS_ALLOW256.
Другая причина, по которой первому элементу палитры следует назначать черный цвет, в том, что первый элемент палитры совпадает с цветом, используемым на экране за пределами нормальной области рисования (overscan color — цвет внешней рамки). Хотя не существует никаких формальных причин, по которым этим цветом должен быть именно черный, другие варианты обычно выглядят довольно странно.

После завершения очистки поверхность bmpsurf (анимационная поверхность) копируется на вторичный буфер функцией DirectDrawWin::BltSurface() (мы используем функцию BltSurface() из-за наличия в ней встроенной поддержки отсечения, что позволяет перемещаемому растру частично выходить за пределы экрана). После выполнения блиттинга рассчитывается новое положение растра, при этом размеры текущего видеорежима используются для ограничения перемещений.
Затем копируется поверхность меню. Она всегда выводится в левом верхнем углу экрана, а ее размеры совпадают с размерами видеорежима с наименьшим разрешением (320x200), так что отсечение не понадобится. Следовательно, мы можем воспользоваться функцией 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. Как мы видели во время рассмотрения ее кода, две из трех поверхностей программы имеют цветовые ключи для отображения прозрачных пикселей. Однако при запуске программы кажется, что анимационная поверхность (не имеющая цветового ключа) тоже является прозрачной. Почему? Потому что цвет фона растра (черный) совпадает с цветом вторичного буфера. Если изменить значение для заливки вторичного буфера, станет ясно, что анимационная поверхность на самом деле непрозрачна.




Давайте посмотрим, как в программе 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. При выводе поверхность меню частот выравнивается по центру поверхности меню видеорежимов.




Функция DrawScene() обновляет экран в зависимости от состояния логической переменной update_screen. Если переменная update_screen равна FALSE, предполагается, что содержимое первичной поверхности не устарело, и делать ничего не нужно. Функция DrawScene() выглядит так:

void BmpViewWin::DrawScene(){ if (update_screen && bmpsurf) { ClearSurface( backsurf, 0 ); BltSurface( backsurf, bmpsurf, x, y ); primsurf->Flip( 0, DDFLIP_WAIT ); update_screen=FALSE; } }

Поскольку текущее положение поверхности рассчитывается в другом месте программы, а функция BltSurface() при необходимости автоматически выполняет отсечение, функция DrawScene() реализуется просто. Если переменная update_screen равна TRUE и существует поверхность для вывода, экран обновляется. Если поверхность не заполняет экран целиком, содержимое вторичного буфера стирается; если заполняет, то в стирании буфера нет необходимости. Затем функция BltSurface() копирует поверхность на вторичный буфер, а функция Flip() отображает изменения на экране. После того как обновление будет завершено, переменной update_screen присваивается значение FALSE.


Содержание раздела