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

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


Перед тем как начинать работу, необходимо создать экземпляры всех основных классов приложения. В нашем случае это будут классы BounceWin и BounceApp. Объект приложения создается способом, традиционным для MFC, то есть объявлением глобального экземпляра:


BounceApp theapp;


Класс BounceApp наследует свои функциональные возможности от DirectDrawApp, и больше ему почти ничего не требуется. Есть всего одно исключение: класс BounceApp отвечает за создание объекта BounceWin. Это происходит в функции InitInstance(), вызываемой MFC при запуске приложения. Функция InitInstance() выглядит так:


BOOL BounceApp::InitInstance() { BounceWin* win = new BounceWin; if (!win->Create( "High Performance Bounce Demo", IDI_ICON )) { AfxMessageBox("Failed to create window"); return FALSE; } m_pMainWnd = win; return DirectDrawApp::InitInstance(); }


Функция InitInstance() создает экземпляр класса BounceWin и вызывает функцию BounceWin::Create(). При вызове Create() необходимо передать два аргумента: строку с названием окна и идентификатор ресурса значка. Хотя название окна не отображается во время работы приложения (потому что приложение занимает весь экран и не имеет строки заголовка), оно будет выводиться в списке задач, а также на панели задач при сворачивании приложения. Если вызов Create() закончится неудачей, то функция InitInstance() возвращает FALSE. По этому признаку MFC узнает о том, что приложение следует аварийно завершить.

Затем переменная m_pMainWnd инициализируется указателем на созданный объект окна. Эта переменная принадлежит классу CWinApp; инициализируя ее, вы сообщаете классу CWinApp о том, каким объектом окна он будет управлять. Если m_pMainWnd не будет присвоен указатель на окно, MFC завершает приложение с ошибкой.

Наконец, мы вызываем функцию DirectDrawApp:InitInstance() и используем полученное от нее значение в качестве результата функции BounceApp::InitInstance(). Функция InitInstance() класса DirectDrawApp выглядит так:


BOOL DirectDrawApp::InitInstance() { ASSERT(m_pMainWnd); m_pMainWnd->ShowWindow(SW_SHOWNORMAL); m_pMainWnd->UpdateWindow(); ShowCursor(FALSE); return TRUE; }

<
br>



Я уже упоминал о том, что MFC требует задать значение переменной m_pMainWnd, но поскольку значение m_pMainWnd используется в этой функции, проверку можно выполнить и самостоятельно. Макрос MFC ASSERT() проверяет значение переменной m_pMainWnd. Если указатель равен нулю, приложение завершается с ошибкой. Если он отличен от нуля, мы вызываем две функции созданного окна: ShowWindow() и UpdateWindow(). Эти функции отображают окно на экране. Наконец, функция ShowCursor() отключает курсор мыши.

Создание и отображение окна завершает процесс инициализации классов DirectDrawApp и BounceApp. Теперь давайте посмотрим, как этот процесс отражается на классах DirectDrawWin и BounceWin.

Как мы уже знаем, функция Create() вызывается из функции BounceApp:: InitInstance(). Она не реализуется классом BounceWin, а наследуется от DirectDrawWin. Функция Create() выглядит так:

BOOL DirectDrawWin::Create(const CString& title,int icon) { CString sClassName; sClassName = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW, LoadCursor(0, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon))); return CWnd::CreateEx(WS_EX_TOPMOST, sClassName, title, WS_POPUP, 0,0, 100, 100, 0, 0); }

Сначала функция Create() регистрирует класс окна с помощью функции AfxRegisterWndClass(). Затем она вызывает функцию CreateEx(), в которой и происходит фактическое создание окна.

Обратите внимание на то, что создаваемое окно имеет размеры 100x100 (седьмой и восьмой аргументы CreateEx()). Такой размер выбран произвольно. DirectDraw при подключении окна автоматически изменяет его размер так, чтобы оно занимало весь экран. Также обратите внимание на флаг WS_EX_TOPMOST: окно полноэкранного приложения DirectDraw должно выводиться поверх остальных окон.

Атрибут верхнего окна, а также занятие им всего экрана необходимы для того, чтобы механизм GDI не смог ничего вывести на экран. GDI ничего не знает о DirectDraw, поэтому наше окно «обманывает» GDI на то время, пока весь экран находится под управлением DirectDraw.Вообще говоря, вывод средствами GDI может происходить и в полноэкранном режиме, но обычно это не рекомендуется, потому что вывод GDI может попасть на невидимую поверхность. Эта тема более подробно рассматривается в главе 5.



Второй аргумент представляет собой адрес указателя на интерфейс 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=%d\n", clientrect.Width(), clientrect.Height() );

}

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




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