Программирование графики с использованием Direct3D


Программирование графики с использованием Direct3D

         

DirectDraw


DirectDraw— это общецелевой API управления видеокартами, позволяющий определять и использовать любые поддерживаемые видеокартами возможности двумерной графики. Кроме того, DirectDraw может программно эмулировать возможности, отсутствующие в конкретной модели видеокарты.

Приведенное ниже описание DirectDraw не претендует на полноту, но это и не требуется. Мы обсудим только те функциональные возможности DirectDraw, которые потребуются нам для создания полноэкранного приложения.



Добавление поддержки видеорежимов


Полноэкранная версия класса RMWin предоставляет несколько функций, которые облегчают обнаружение и переключение видеорежимов. Вот эти функции:

GetNumDisplayModes() ActivateDisplayMode() GetCurDisplayMode() GetDisplayModeDims() GetCurDisplayModeDims()

Внутри класса RMWin собирается список поддерживаемых видеорежимов. Количество элементов в этом списке может быть определено с помощью функции GetNumDisplayModes(). Заданный видеорежим может быть активирован функцией ActivateDisplayMode(). Функция GetCurDisplayMode() возвращает включенный в данный момент видеорежим. Функции GetDisplayModeDims() и GetCurDisplayModeDims() возвращают параметры видеорежима (ширина, высота и глубина цвета экрана).





Функции работы с палитрой


Класс RMWin предоставляет две функции, которые содействуют использованию палитр. Функция UsePalette() является защищенной функцией, позволяющей классу RMWin использовать палитру, хранящуюся в файле BMP. Функция UsePalette() определена в объявлении класса RMWin:

void UsePalette(CString filename) { palettefile = filename; }

Функция просто сохраняет полученное имя файла. Файл будет использован классом RMWin когда необходимо будет создать палитру.

Вспомните, что функция OnCreate() вызывала закрытую функцию InstallPalette(). Функция InstallPalette() использует имя файла, предоставленное функцией UsePalette() для извлечения и установки палитры, основываясь на содержимом файла BMP. Код функции InstallPalette() показан в листинге 10.6.

Листинг 10.6. Функция InstallPalette()

BOOL RMWin::InstallPalette() { BITMAPFILEHEADER bmpfilehdr; BITMAPINFOHEADER bmpinfohdr; RGBQUAD quad[256]; PALETTEENTRY pe[256]; int ncolors; if (palettefile.GetLength() <= 0) return FALSE; if (modedepth != 8) return FALSE; if (palette) { palette->Release(); palette = 0; } ifstream bmp(palettefile, ios::binary | ios::nocreate); bmp.read((char*)&bmpfilehdr, sizeof(bmpfilehdr)); bmp.read((char*)&bmpinfohdr, sizeof(bmpinfohdr)); char* ptr = (char*)&bmpfilehdr.bfType; if (*ptr != 'B' || *++ptr != 'M') { TRACE("invalid bitmap\n"); return FALSE; } if (bmpinfohdr.biBitCount != 8) { TRACE("not 8 bit file!\n"); return FALSE; } if (bmpinfohdr.biClrUsed == 0) ncolors = 256; else ncolors = bmpinfohdr.biClrUsed; bmp.read((char*)quad, sizeof(RGBQUAD) * ncolors); for(int i = 0; i < ncolors; i++) { pe[i].peRed = quad[i].rgbRed; pe[i].peGreen = quad[i].rgbGreen; pe[i].peBlue = quad[i].rgbBlue; pe[i].peFlags = D3DPAL_READONLY; } HRESULT r = ddraw->CreatePalette(DDPCAPS_8BIT, pe, &palette, 0); if (r != DD_OK) { TRACE("failed to load palette data from file\n"); return FALSE; } primsurf->SetPalette(palette); backsurf->SetPalette(palette); return TRUE; }

Перед обсуждением функции InstallPalette(), следует упомянуть, что мы будем извлекать только ту часть файла BMP, которая содержит палитру. Хранящееся в файле изображение игнорируется.

Сначала в функции InstallPalette() объявляется несколько локальных переменных:

BITMAPFILEHEADER bmpfilehdr; BITMAPINFOHEADER bmpinfohdr; RGBQUAD quad[256]; PALETTEENTRY pe[256];

Структура BITMAPFILEHEADER присутствует в начале каждого файла BMP. Мы используем эту структуру для загрузки параметров конкретного файла BMP. В частности, структура BITMAPFILEHEADER содержит сигнатуру, позволяющую идентифицировать файл BMP. Определение структуры BITMAPFILEHEADER выглядит следующим образом:

typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER;

В корректном файле BMP поле bfType содержит символы «BM». Если эта сигнатура отсутствует, мы будем знать, что загруженный файл имеет неверный формат.

Структура BITMAPINFOHEADER располагается в файле BMP сразу после структуры BITMAPFILEHEADER. Эта структура используется для загрузки параметров хранящегося в файле изображения. Определение структуры BITMAPINFOHEADER выглядит следующим образом:

typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;

Поля biWidth и biHeight содержат размеры изображения. Поле biBitCount указывает глубину цвета изображения. Оно используется, чтобы убедиться, что загружаемый файл содержит 8-битовое изображение.

Объявление массива структур RGBQUAD включено потому, что файл BMP хранит данные палитры в виде элементов RGBQUAD. Объявленный массив содержит 256 элементов, поскольку это максимально возможное количество цветов, которое может храниться в файле.

Затем объявлен массив структур PALETTEENTRY. Мы используем этот массив для создания палитры DirectDraw. Структуры RGBQUAD и PALETTEENTRY очень похожи. Их определение выглядит так:

typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD; typedef struct tagPALETTEENTRY { BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY;

Главное различие — порядок следования красной, зеленой и синей цветовых составляющих. Для копирования содержимого массива RGBQUAD в массив PALETTEENTRY будет использован цикл.

Сначала функция InstallPalette() проверяет строку palettefile:

if (palettefile.GetLength() <= 0) return FALSE;

Вспомните, что значение строки palettefile устанавливается в функции UsePalette(). Если класс, производный от RMWin не использует UsePalette() для объявления имени файла BMP, палитра не создается и функция InstallPalette() возвращает FALSE. Это не так плохо, как может показаться, поскольку палитра требуется только для 8-разрядных видеорежимов. Приложения, использующие только 16-, 24- и 32-разрядные видеорежимы будут правильно выполняться и без использования функции UsePalette().

Затем проверяется глубина цвета в текущем видеорежиме:

if (modedepth != 8) return FALSE;

Палитра не требуется для тех видеорежимов, глубина цвета которых отличается от восьми, и в этом случае функция InstallPalette() завершает работу.

Потом функция InstallPalette() освобождает любые существующие палитры:

if (palette) { palette->Release(); palette = 0; }

Далее открывается файл BMP и загружаются структуры, содержащие параметры файла и изображения:

ifstream bmp(palettefile, ios::binary | ios::nocreate); bmp.read((char*)&bmpfilehdr, sizeof(bmpfilehdr)); bmp.read((char*)&bmpinfohdr, sizeof(bmpinfohdr));

Загруженные данные используются при проверке сигнатуры файла BMP и глубине цвета изображения, содержащегося в файле:

char* ptr = (char*)&bmpfilehdr.bfType; if (*ptr != 'B' || *++ptr != 'M') { TRACE("invalid bitmap\n"); return FALSE; } if (bmpinfohdr.biBitCount != 8) { TRACE("not 8 bit file!\n"); return FALSE; }

Если отсутствует сигнатура «BM», значит, файл поврежден, или это вообще не файл BMP. В этом случае выводится сообщение об ошибке, и функция возвращает FALSE.

Файлы, глубина цветности изображений в которых меньше восьми, бесполезны для нас. Например, четырехбитные файлы используют только 16 цветов, чего явно недостаточно для 256-цветного видеорежима. Файлы, в которых глубина цвета изображения больше или равна 16 вообще не содержат палитры. Если глубина цвета содержащегося в файле изображения не равна восьми, функция завершает работу.

Затем вычисляется количество цветов в палитре:

if (bmpinfohdr.biClrUsed == 0) ncolors = 256; else ncolors = bmpinfohdr.biClrUsed;

Бывает, что значение поля biClrUsed равно нулю. Это указывает, что файл содержит максимально возможное для данной глубины количество цветов. Если значение поля biClrUsed равно нулю, мы присваиваем переменной ncolors значение 256. В противном случае мы просто присваиваем значение поля biClrUsed переменной ncolors.

На следующем шаге функция InstallPalette() выполняет загрузку палитры с диска и инициализирует массив структур PALETTEENTRY:

bmp.read((char*)quad, sizeof(RGBQUAD) * ncolors ); for( int i = 0; i < ncolors; i++) { pe[i].peRed = quad[i].rgbRed; pe[i].peGreen = quad[i].rgbGreen; pe[i].peBlue = quad[i].rgbBlue; pe[i].peFlags = D3DPAL_READONLY; }

Палитра загружается в массив quad. Затем в цикле элементы массива quad копируются в массив pe. Константа D3DPAL_READONLY используется для указания, что цвета в массиве не должны изменяться.

Теперь мы можем создать палитру DirectDraw:

HRESULT r = ddraw->CreatePalette(DDPCAPS_8BIT, pe, &palette, 0); if (r != DD_OK) { TRACE("failed to load palette data from file\n"); return FALSE; }

Палитра создается функцией CreatePalette() интерфейса DirectDraw. Константа DDPCAPS_8BIT указывает DirectDraw, что данные предоставленной палитры являются 8-разрядными. Массив pe передается во втором аргументе. Третий аргумент функции CreatePalette() — это адрес указателя на новую палитру.

В заключение новая палитра присоединяется к первичной и вторичной поверхностям:

primsurf->SetPalette(palette); backsurf->SetPalette(palette);

На этом работа функции InstallPalette() завершается. Файл BMP закрывается автоматически при выходе из функции, поскольку объект ifstream, используемый для открытия файла, выходит из области видимости.



Функции работы с поверхностями


Класс RMWin создает и обслуживает поверхности DirectDraw, необходимые для полноэкранных операций Direct3D. Как вы увидите позже в этой главе, иногда полезно создавать и отображать дополнительные поверхности. Для этого в класс RMWin были добавлены две функции, относящиеся к работе с поверхностями: CreateSurface() и ClearSurface(). Функция CreateSurface() создает новую поверхность указанного размера. Функция ClearSurface() стирает содержимое существующей поверхности.



Функция ActivateDisplayMode()


Класс RMWin предоставляет две версии функции ActivateDisplayMode(). Первая версия объявлена как защищенная и используется производными классами, чтобы сообщить классу RMWin о необходимости включения указанного видеорежима. Вторая версия объявлена как закрытая и используется защищенной версией для выполнения действительного переключения видеорежима. Защищенная версия функции ActivateDisplayMode() выглядит следующим образом:

BOOL RMWin::ActivateDisplayMode(int index) { DWORD w = displaymode[index].width; DWORD h = displaymode[index].height; DWORD d = displaymode[index].depth; curdisplaymode = index; return ActivateDisplayMode(w, h, d); }

В качестве аргумента функции ActivateDisplayMode() передается единственное целое число. Это число является индексом элемента в списке поддерживаемых видеорежимов, созданном функцией InitDisplayMode().

ActivateDisplayMode() использует переменную index для получения параметров видеорежима из указанного элемента массива displaymode. Затем обновляется значение закрытой переменной curdisplaymode и вызывается закрытая версия функции ActivateDisplayMode(). Код закрытой версии функции ActivateDisplayMode() приведен в листинге 10.7.

Листинг 10.7. Закрытая версия функции ActivateDisplayMode()

BOOL RMWin::ActivateDisplayMode(DWORD w,DWORD h,DWORD d) { if (modewidth == w && modeheight == h && modedepth == d) return TRUE; modewidth = w; modeheight = h; modedepth = d; if (scene) { scene->Release(); scene = 0; } if (device) { device->Release(); device = 0; } if (primsurf) { primsurf->Release(); primsurf = 0; } if (zbufsurf) { zbufsurf->Release(); zbufsurf = 0; } ddraw->SetDisplayMode(modewidth, modeheight, modedepth); InitMainSurfaces(); InstallPalette(); CreateDevice(); d3drm->CreateFrame(0, &scene); CreateScene(); return TRUE; }

Эта версия более сложна чем предыдущая, поскольку отвечает за уничтожение и повторное создание внутренних объектов приложения.

Сперва следует проверить, не активирован ли уже запрашиваемый видеорежим. Если параметры запрашиваемого видеорежима не отличаются от параметров текущего видеорежима, функция завершает работу. Если запрошен новый видеорежим, обновляются значения переменных modewidth, modeheight и modedepth.

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

Теперь для активации нового видеорежима вызывается функция SetDisplayMode() интерфейса DirectDraw. В качестве аргументов ей передаются переменные modewidth, modeheight и modedepth.

Следующие пять вызовов функций аналогичны фрагменту функции OnCreate(), следующему за вызовом функции InitDisplayMode():

InitMainSurfaces(); InstallPalette(); CreateDevice(); d3drm->CreateFrame(0, &scene); CreateScene();

В функции OnCreate() эти пять вызовов инициализируют приложение в соответствии с начальным видеорежимом. В данном случае эти вызовы изменяют параметры приложения согласно изменению видеорежима.



Функция ClearSurface()


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

bool RMWin::ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD clr) { DDBLTFX bltfx; memset(&bltfx, 0, sizeof(bltfx)); bltfx.dwSize = sizeof(bltfx); bltfx.dwFillColor = clr; surf->Blt(0, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &bltfx); return TRUE; }

Функция получает два аргумента: указатель на очищаемую поверхность и значение, используемое для очистки поверхности.

Для удаления содержимого поверхности используется функция Blt() интерфейса DirectDrawSurface. Обычно функция Blt() применяется для копирования поверхностей или их частей на другие поверхности. Тем не менее, функция Blt() может выполнять специальные операции, такие как зеркальное отражение, вращение и операции с Z-буфером. В нашем случае мы используем структуру DDBLTFX и константу DDBLT_COLORFILL чтобы сообщить функции Blt(), что мы хотим к указанной поверхности применить операцию заливки заданным цветом.



Функция CreateDevice()


Функция CreateDevice() вызывается из функции OnCreate(). Ее задачей является создание устройства Direct3D:

BOOL RMWin::CreateDevice() { d3drm->CreateDeviceFromSurface(0, ddraw, backsurf, &device); device->SetQuality(D3DRMRENDER_GOURAUD); return TRUE; }

Для создания устройства используется функция CreateDeviceFromSurface(). Она получает четыре аргумента. Первый— это GUID (Глобальный Уникальный Идентификатор) идентифицирующий устройство. Использование нулевого значения позволяет Direct3D выбрать устройство автоматически. Вам потребуется указывать конкретный GUID только в том случае, если вы хотите изменить выбор устройства, делаемый Direct3D по умолчанию. Для получения конкретных GUID может использоваться функция GetGUID(), которая обсуждалась в главе 4.

Второй аргумент функции CreateDeviceFromSurface() — это указатель на интерфейс DirectDraw. Третий аргумент — поверхность, используемая для создания устройства. Мы используем поверхность backsurf, которая была создана в функции InitMainSurfaces(). Это означает, что Direct3D будет использовать поверхность backsurf для визуализации.

Последний аргумент функции CreateDeviceFromSurface() — это адрес указателя device. После создания устройства вызывается функция SetQuality() интерфейса Direct3DRMDevice чтобы разрешить использование метода визуализации Гуро (по умолчанию используется плоский метод визуализации).



Функция CreateFPSSurface()


Функция CreateFPSSurface() подходит к созданию поверхности немного по другому, чем функция CreateMenuSurface(). Функция CreateFPSSurface() сначала определяет размер поверхности, и только потом создает ее, как показано в листинге 10.12. Размеры поверхности зависят от размера выводимого текста.

Листинг 10.12. Функция CreateFPSSurface()

BOOL FullScreenWin::CreateFPSSurface() { static const char dummystr[] = "FPS: 0000"; HDC hdc = ::GetDC(0); SelectObject(hdc, smallfont); SIZE size; GetTextExtentPoint(hdc, dummystr, strlen(dummystr), &size); ::ReleaseDC(0, hdc); fpsrect.left = 0; fpsrect.top = 0; fpsrect.right = size.cx; fpsrect.bottom = size.cy; fpssurf = CreateSurface(size.cx, size.cy); DDCOLORKEY ddck; ddck.dwColorSpaceLowValue = 0; ddck.dwColorSpaceHighValue = 0; fpssurf->SetColorKey(DDCKEY_SRCBLT, &ddck); return TRUE; }

Для определения размера текста используется строка-макет, созданная с учетом худшего случая. Эта строка содержит больше символов, чем строки, которые будут действительно отображаться на экране. Поэтому размер текста будет достаточен для вывода любого значения частоты кадров (вплоть до 9 999 кадров в секунду).

Для вычисления размеров текста создается временный контекст устройства. Выбирается меньший из двух шрифтов приложения, и вызывается функция GetTextExtentPoint() для получения размера текста в пикселях. Затем размер текста используется при инициализации структуры fpsrect и создании поверхности fpssurf.

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



Функция CreateMenuSurface()


Функция CreateMenuSurface() выглядит следующим образом:

BOOL FullScreenWin::CreateMenuSurface() { menusurf = CreateSurface(menuwidth, menuheight); menurect.left = 0; menurect.top = 0; menurect.right = menuwidth; menurect.bottom = menuheight; DDCOLORKEY ddck; ddck.dwColorSpaceLowValue = 0; ddck.dwColorSpaceHighValue = 0; menusurf->SetColorKey(DDCKEY_SRCBLT, &ddck); return TRUE; }

Сначала для создания поверхности используется функция RMWin::CreateSurface(). В качестве аргументов функции используются константы menuwidth и menuheight. Функция CreateSurface() возвращает указатель на новую поверхность.

Затем инициализируется структура menurect. Прямоугольник не описывает местоположение поверхности, а только ее размеры.

Затем поверхности назначается цветовой ключ. Цветовой ключ (color key) указывает, какие значения пикселей должны интерпретироваться как прозрачные. В нашем случае мы указываем, что прозрачными должны считаться любые пиксели поверхности меню видеорежимов с нулевым значением. Если вы взглянете на работающее приложение FullScreen, то заметите, что вся поверхность меню, за исключением текста, прозрачна. Это вызвано тем, что перед выводом текста всем пикселям поверхности присваивается нулевое значение.

Говоря о содержимом поверхности, следует упомянуть, что только что созданная поверхность содержит случайное содержимое. При создании поверхности выделяется память для ее размещения, но инициализация этой памяти не производится.



Функция CreateScene()


Функция CreateScene() представлена в листинге 10.10.

Листинг 10.10. Функция CreateScene()

BOOL FullScreenWin::CreateScene() { //-------- ПОВЕРХНОСТИ МЕНЮ И FPS -------- selectmode = GetCurDisplayMode(); CreateMenuSurface(); UpdateMenuSurface(); CreateFPSSurface(); // ------- СЕТКА -------- D3DRMLOADRESOURCE resinfo; resinfo.hModule = 0; resinfo.lpName = MAKEINTRESOURCE(IDR_SWIRLMESH); resinfo.lpType = "MESH"; d3drm->CreateMeshBuilder(&meshbuilder); meshbuilder->Load(&resinfo, 0, D3DRMLOAD_FROMRESOURCE, 0, 0); ScaleMesh(meshbuilder, D3DVALUE(15)); //------- ФРЕЙМ СЕТКИ -------- LPDIRECT3DRMFRAME meshframe; d3drm->CreateFrame(scene, &meshframe); meshframe->AddVisual(meshbuilder); meshframe->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.1)); meshframe->AddMoveCallback(UpdateAnimation, 0); //-------- АНИМАЦИЯ -------- d3drm->CreateAnimation(&animation); for (int i = 0; i < 11; i++) { D3DRMQUATERNION quat; D3DRMQuaternionFromRotation(&quat, &vect[i], rot[i]); animation->AddRotateKey(D3DVALUE(i), &quat); animation->AddPositionKey(D3DVALUE(i), trans[i].x, trans[i].y, trans[i].z); } animation->SetOptions(D3DRMANIMATION_SPLINEPOSITION | D3DRMANIMATION_CLOSED | D3DRMANIMATION_POSITION | D3DRMANIMATION_SCALEANDROTATION); animation->SetFrame(meshframe); meshframe->Release(); meshframe = 0; // -------- НАПРАВЛЕННЫЙ СВЕТ -------- LPDIRECT3DRMFRAME dlightframe; LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); d3drm->CreateFrame(scene, &dlightframe); dlightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); dlightframe->AddLight(dlight); dlightframe->Release(); dlightframe = 0; dlight->Release(); dlight = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }

Функция CreateScene() выполняет следующие шесть действий:

Инициализация и отображение поверхностей меню и счетчика FPS. Создание и загрузка сетки. Создание фрейма для сетки. Создание и настройка анимационной последовательности с использованием интерфейса Direct3DRMAnimation. Создание источника света. Создание порта просмотра.

Мы не будем обсуждать этапы со 2 по 6, поскольку они неоднократно рассматривались в предыдущих главах. Сейчас для нас более интересен первый этап, на котором осуществляется инициализация поверхностей для меню видеорежимов и счетчика FPS. Поверхности инициализируются следующими вызовами функций:

selectmode = GetCurDisplayMode(); CreateMenuSurface(); UpdateMenuSurface(); CreateFPSSurface();

Функция GetCurDisplayMode() используется для инициализации переменной selectmode. Это делается для того, чтобы в меню был выделен текущий видеорежим. Функция CreateMenuSurface() создает поверхность, которая будет использована для отображения меню видеорежимов. Функция UpdateMenuSurface() формирует содержимое для поверхности меню видеорежимов. Функция CreateFPSSurface() создает поверхность для счетчика FPS, но само значение FPS пока не рассчитывается, поскольку еще не было выведено ни одного кадра.



Функция CreateSurface()


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

LPDIRECTDRAWSURFACE RMWin::CreateSurface(DWORD w, DWORD h) { DDSURFACEDESC desc; memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(desc); desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; desc.dwWidth = w; desc.dwHeight = h; desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; LPDIRECTDRAWSURFACE surf; HRESULT r = ddraw->CreateSurface(&desc, &surf, 0); if (r != DD_OK) return 0; return surf; }

Локальная структура DDSURFACEDESC используется для описания внеэкранной поверхности с размерами, равными полученным параметрам. Функция CreateSurface() интерфейса DirectDraw создает новую поверхность. Если вызов функции CreateSurface() завершен успешно, будет возвращен указатель на новую поверхность.

Функция CreateSurface() создана для использования в классах, производных от класса RMWin, но не используется самим классом RMWin. Мы воспользуемся этой функцией в приложении FullScreen.



Функция FullScreenWin()


Код конструктора класса FullScreenWin приведен в листинге10.9.

Листинг 10.9. Функция FullScreenWin()

FullScreenWin::FullScreenWin() { meshbuilder = 0; animation = 0; menusurf = 0; fpssurf = 0; selectmode = -1; displayfps = FALSE; UsePalette("palette.bmp"); largefont = CreateFont(28, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Arial"); smallfont = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, "Arial"); }

Сначала выполняется инициализация переменных класса. Затем вызывается функция UsePalette(), чтобы указать имя файла BMP, содержащего палитру, которая будет использоваться в 8-разрядных видеорежимах.

Два дескриптора шрифтов, largefont и smallfont, инициализируются функцией Win32 CreateFont(). Единственное различие между этими двумя шрифтами — их размер. Крупный шрифт используется для отображения названия приложения (в верхней части меню видеорежимов). Мелкий шрифт используется для вывода элементов меню видеорежимов и для вывода счетчика FPS.



Функция GetCurDisplayMode()


Функция GetCurDisplayMode() возвращает индекс включенного в данный момент видеорежима:

int GetCurDisplayMode() { return curdisplaymode; }

Как и в случае с функцией GetNumDisplayModes(), простота функции GetCurDisplayMode() делает ее хорошим кандидатом на определение и объявление внутри определения класса.



Функция GetCurDisplayModeDims()


Определение функции GetCurDisplayModeDims() выглядит следующим образом:

BOOL RMWin::GetCurDisplayModeDims(DWORD& w, DWORD& h, DWORD& d) { if (curdisplaymode < 0 || curdisplaymode >= totaldisplaymodes) return FALSE; w = displaymode[curdisplaymode].width; h = displaymode[curdisplaymode].height; d = displaymode[curdisplaymode].depth; return TRUE; }

Функция почти полностью идентична функции GetDisplayModeDims(), за исключением того, что возвращает параметры текущего, а не указанного в аргументе, видеорежима. Это вспомогательная функция. Те же самые данные можно получить, используя функцию GetDisplayModeDims() совместно с функцией GetCurDisplayMode().



Функция GetDisplayModeDims()


Функция GetDisplayModeDims() возвращает параметры указанного видеорежима:

BOOL RMWin::GetDisplayModeDims(int index, DWORD& w, DWORD& h, DWORD& d) { if (index < 0 || index >= totaldisplaymodes) return FALSE; w = displaymode[index].width; h = displaymode[index].height; d = displaymode[index].depth; return TRUE; }

Первый параметр функции GetDisplayModeDims() является индексом интересующего видеорежима. Индекс используется при получении параметров видеорежима из массива displaymode.

Позднее в этой главе мы воспользуемся данной функцией для получения параметров всех поддерживаемых видеорежимов. Эти параметры будут использованы при создании меню видеорежимов.



Функция GetNumDisplayModes()


Функция GetNumDisplayModes()— это защищенная функция, возвращающая количество видеорежимов, обнаруженных функцией InitDisplayMode(). Функция объявлена и определена в определении класса RMWin:

int GetNumDisplayModes() { return totaldisplaymodes; }



Функция InitDisplayMode()


Функция InitDisplayMode() использует DirectDraw чтобы определить, какие видеорежимы поддерживаются установленной видеокартой. Эти данные используются для создания списка видеорежимов. Затем этот список будет применяться для определения начального видеорежима. Код функции InitDisplayMode() приведен в листинге 10.3.

Листинг 10.3. Функция InitDisplayMode()

BOOL RMWin::InitDisplayMode() { curdisplaymode = 0; CDC* dc = GetDC(); DWORD curdisplaydepth = dc->GetDeviceCaps(BITSPIXEL); dc->DeleteDC; ddraw->EnumDisplayModes(0, 0, 0, DisplayModeAvailable); qsort(displaymode, totaldisplaymodes, sizeof(videomode), CompareModes); for (int i = 0; i < totaldisplaymodes; i++) { DWORD w, h, d; GetDisplayModeDims(i, w, h, d); if (w == 640 && h == 480 && d == curdisplaydepth) curdisplaymode = i; } GetDisplayModeDims(curdisplaymode, modewidth, modeheight, modedepth); ddraw->SetDisplayMode(modewidth, modeheight, modedepth); return totaldisplaymodes != 0; }

Сначала функция определяет глубину пикселей текущего видеорежима Windows. Это делается с помощью функции GetDeviceCaps(). Мы используем константу BITSPIXEL чтобы указать, что нас интересует количество бит, необходимое для представления одной точки изображения. Полученное значение сохраняется в переменной curdisplaydepth. Мы используем его позднее при выборе начального видеорежима.

Затем вызывается функция EnumDisplayModes() интерфейса DirectDraw. Последний аргумент функции EnumDisplayModes() — это функция обратного вызова, которую DirectDraw будет вызывать каждый раз при обнаружении поддерживаемого видеорежима. Для регистрации обнаруженных видеорежимов функция InitDisplayMode() использует следующую функцию обратного вызова:

HRESULT WINAPI RMWin::DisplayModeAvailable(LPDDSURFACEDESC desc, LPVOID) { int& count = totaldisplaymodes; if (count == MAXDISPLAYMODES) return DDENUMRET_CANCEL; displaymode[count].width = desc->dwWidth; displaymode[count].height = desc->dwHeight; displaymode[count].depth = desc->ddpfPixelFormat.dwRGBBitCount; count++; return DDENUMRET_OK; }

Функция обратного вызова DisplayModeAvailable() получает указатель на структуру DDSURFACEDESC. Эта структура описывает обнаруженный режим. Функция обратного вызова использует ее для инициализации элементов массива displaymode. После того, как элемент будет инициализирован, увеличивается переменная totaldisplaymodes (через псевдоним count). Функция возвращает константу DDENUMRET_OK, чтобы указать, что DirectDraw должен продолжить поиск поддерживаемых видеорежимов. Использование в качестве возвращаемого значения константы DDRNUMRET_CANCEL приведет к прекращению перечисления видеорежимов DirectDraw.

Вернемся к функции InitDisplayMode(). После обнаружения всех поддерживаемых видеорежимов вызывается функция Win32 qsort() чтобы выполнить сортировку полученного массива видеорежимов. Вызов функции выглядит следующим образом:

qsort(displaymode, totaldisplaymodes, sizeof(videomode), CompareModes);

Функции qsort() передаются массив displaymode, общее количество обнаруженных видеорежимов, размер каждого элемента массива displaymode и функция сравнения. Функция сравнения (CompareModes()) — это функция обратного вызова, вызываемая из qsort() для определения правильного порядка следования элементов массива. Текст функции CompareModes() приведен в листинге 10.4.

Листинг 10.4. Функция CompareModes()

int RMWin::CompareModes(const void *arg1, const void *arg2) { videomode* mode1 = (videomode*)arg1; videomode* mode2 = (videomode*)arg2; DWORD volume1 = mode1->width * mode1->height; DWORD volume2 = mode2->width * mode2->height; if (volume1 < volume2) return -1; else if (volume1 > volume2) return 1; if (mode1->depth < mode2->depth) return -1; else if (mode1->depth > mode2->depth) return 1; return 0; }

Для сравнения двух видеорежимов, передаваемых в параметрах функции CompareModes() используются их размерности. Благодаря этому массив displaymode будет отсортирован по размеру экрана в видеорежимах.

Затем из массива displaymode выбирается начальный видеорежим:

for (int i = 0; i < totaldisplaymodes; i++) { DWORD w, h, d; GetDisplayModeDims(i, w, h, d); if (w == 640 && h == 480 && d == curdisplaydepth) curdisplaymode = i; }

Ищется видеорежим 640x480 (он выбран в качестве начального, поскольку практически любая видеокарта поддерживает режим 640x480). В качестве начальной глубины пикселей используется глубина пикселей текущего видеорежима Windows.

Сразу после выбора видеорежима его параметры извлекаются и используются для изменения текущего видеорежима:

GetDisplayModeDims(curdisplaymode, modewidth, modeheight, modedepth); ddraw->SetDisplayMode(modewidth, modeheight, modedepth);

Функция GetDisplayModeDims() извлекает параметры видеорежима, заданного первым аргументом. Переменные modewidth, modeheight и modedepth принадлежат классу RMWin и используются для хранения параметров текущего видеорежима. Полученные параметры видеорежима передаются в аргументах функции SetDisplayMode() интерфейса DirectDraw. Этот вызов функции осуществляет действительное переключение видеорежима и завершает работу функции InitDisplayMode().



Функция InitMainSurfaces()


Пришло время взглянуть на функцию InitMainSurfaces(). Хочу напомнить, что функция InitMainSurfaces() вызывается из функции OnCreate() сразу после обращения к функции InitDisplayMode(). Функция InitMainSurfaces() создает первичную и вторичную поверхности вместе с Z-буфером. Код функции приведен в листинге10.5.

Листинг 10.5. Функция InitMainSurfaces()

BOOL RMWin::InitMainSurfaces() { if (primsurf) { primsurf->Release(); primsurf = 0; } if (zbufsurf) { zbufsurf->Release(); zbufsurf = 0; } DDSURFACEDESC desc; desc.dwSize = sizeof(desc); desc.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS; desc.dwBackBufferCount = 1; desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddraw->CreateSurface(&desc, &primsurf, 0); DDSCAPS ddscaps; ddscaps.dwCaps = DDSCAPS_BACKBUFFER; primsurf->GetAttachedSurface(&ddscaps, &backsurf); memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(DDSURFACEDESC); desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_ZBUFFERBITDEPTH; desc.dwWidth = modewidth; desc.dwHeight = modeheight; desc.dwZBufferBitDepth = 16; desc.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | DDSCAPS_SYSTEMMEMORY; ddraw->CreateSurface(&desc, &zbufsurf, 0); backsurf->AddAttachedSurface(zbufsurf); return TRUE; }

Сначала функция InitMainSurfaces() освобождает любые существующие первичные поверхности и Z-буферы:

if (primsurf) { primsurf->Release(); primsurf = 0; } if (zbufsurf) { zbufsurf->Release(); zbufsurf = 0; }

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

Затем создается первичная поверхность:

DDSURFACEDESC desc; desc.dwSize = sizeof(desc); desc.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS; desc.dwBackBufferCount = 1; desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddraw->CreateSurface(&desc, &primsurf, 0);

Поверхность, которую мы хотим создать, должна быть описана в структуре DDSURFACEDESC. Поле dwSize этой структуры должно содержать размер структуры. Поле dwFlags используется для хранения набора флагов, указывающих, какие поля структуры мы будем инициализировать. В нашем случае используются поля dwBackBufferCount и ddsCaps, поэтому мы указываем флаги DDSD_BACKBUFFERCOUNT и DDSD_CAPS.

Флаги возможностей поверхности (DDSCAPS_PRIMARYSURFACE, DDSCAPS_FLIP и DDSCAPS_COMPLEX) указывают, что мы создаем первичную поверхность с возможностью переключения страниц. Флаг DDSCAPS_3DDEVICE сообщает DirectDraw, что мы будем использовать новую поверхность для создания устройства Direct3D.

Фактическое создание поверхностей выполняет функция CreateSurface() интерфейса DirectDraw. В качестве первого аргумента мы передаем подготовленную ранее структуру DDSURFACEDESC. Во втором аргументе передается адрес указателя на интерфейс DirectDrawSurface. Указатель (primsurf) является членом класса RMWin и будет использоваться в дальнейшем для выполнения переключения страниц.

Обратите внимание, что значение поля dwBackBufferCount структуры DDSURFACEDESC равно единице. Это означает, что мы проинформировали DirectDraw о наличии у первичной поверхности одного вторичного буфера. Фактически, DirectDraw создает вторичный буфер вместе с первичной поверхностью. Все что нам остается сделать — получить указатель на вторичный буфер:

DDSCAPS ddscaps; ddscaps.dwCaps = DDSCAPS_BACKBUFFER; primsurf->GetAttachedSurface(&ddscaps, &backsurf);

Позднее мы будем использовать указатель backsurf для сохранения выводимого изображения перед его перемещением, или переключением, на первичную поверхность.

Теперь мы создаем Z-буфер и присоединяем его к поверхности backsurf:

memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(DDSURFACEDESC); desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_ZBUFFERBITDEPTH; desc.dwWidth = modewidth; desc.dwHeight = modeheight; desc.dwZBufferBitDepth = 16; desc.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | DDSCAPS_SYSTEMMEMORY; ddraw->CreateSurface(&desc, &zbufsurf, 0); backsurf->AddAttachedSurface(zbufsurf);

Для описания поверхности Z-буфера нам потребуется экземпляр структуры DDSURFACEDESC. Вместо того, чтобы объявлять еще одну копию, воспользуемся уже существующим экземпляром desc, который использовался при создании первичной поверхности. Очистим структуру с помощью функции memset(), присвоив всем ее полям нулевые значения. Затем присвоим требуемые значения полям dwSize и dwFlags.

Флаги, присвоенные полю dwFlags указывают, что мы будем задавать ширину и высоту поверхности, ее возможности, а также глубину Z-буфера. Соответствующим полям структуры присваиваются значения. Размеры Z-буфера должны быть равны размерам первичного и вторичного буферов. Поскольку размеры первичного и вторичного буферов всегда равны размеру экрана в текущем видеорежиме, для присваивания значений полям dwWidth и dwHeight можно использовать переменные modewidth и modeheight.

Мы создаем 16-разрядный Z-буфер, присваивая соответствующее значение полю. Глубина Z-буфера определяет точность работы кода удаления невидимых поверхностей. 8-разрядный Z-буфер может хранить только 256 различных значений Z, или расстояний, поэтому его возможности весьма ограничены. 16-разрядный Z-буфер предоставляет 65 535 различных значений и подходит для большинства случаев Z-буферизации. Сложные сцены могут потребовать наличия 24- или 32-разрядного Z-буфера.

Константа DDSCAPS_SYSTEMMEMORY используется для указания, что Z-буфер должен размещаться в системной памяти, а не в памяти видеокарты. Видеопамяти может не хватать для трехмерной графики (особенно на видеокартах с 2 мегабайтами памяти). Хранение Z-буфера в системной памяти освобождает память видеокарты для хранения отображаемых поверхностей, таких как спрайты и фоновые изображения. Хранение отображаемых данных в видеопамяти более предпочтительно, поскольку ускоряет работу приложения так как видеокарты обычно выполняют копирование блоков видеопамяти гораздо быстрее, чем копирование из системной памяти в видеопамять.

На последнем этапе выполняется создание поверхности Z-буфера и ее присоединение к поверхности backsurf:

ddraw->CreateSurface(&desc, &zbufsurf, 0); backsurf->AddAttachedSurface(zbufsurf);

После того, как поверхность Z-буфера присоединена, Direct3D использует ее автоматически. Больше никаких манипуляций с Z-буфером не требуется.



Функция KeyDown()


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

Листинг 10.15. Функция KeyDown()

void FullScreenWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { static int screencapture; int newindex; int nmodes = GetNumDisplayModes(); if (nmodes > maxmodes) nmodes = maxmodes; int rows = nmodes / menucols; if (nmodes % menucols) rows++; switch (nChar) { case VK_ESCAPE: PostMessage(WM_CLOSE); break; case VK_UP: newindex = selectmode - 1; if (newindex >= 0) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_DOWN: newindex = selectmode + 1; if (newindex < nmodes) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_LEFT: newindex = selectmode - rows; if (newindex >= 0) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_RIGHT: newindex = selectmode + rows; if (newindex < nmodes) { selectmode = newindex; UpdateMenuSurface(); } break; case VK_RETURN: if (menusurf) { menusurf->Release(); menusurf = 0; } if (fpssurf) { fpssurf->Release(); fpssurf = 0; } ActivateDisplayMode(selectmode); displayfps = FALSE; CreateMenuSurface(); UpdateMenuSurface(); CreateFPSSurface(); break; case 'W': OnRenderWireframe(); break; case 'F': OnRenderFlat(); break; case 'G': OnRenderGouraud(); break; } RMWin::OnKeyDown(nChar, nRepCnt, nFlags); }

Если говорить по существу, функция KeyDown() использует клавиши управления курсором для выделения видеорежима в меню видеорежимов. Клавиша ENTER используется для активации выбранного видеорежима. Клавиши W, F и G применяются для изменения метода визуализации единственной сетки приложения (каркасный, плоский или Гуро). Нажатие на клавишу ESC завершает работу приложения.




Функция OnCreate()


Функция OnCreate() вызывается MFC для выполнения инициализации окна. В оконной версии RMWin, мы использовали OnCreate() только для инициализации интерфейса Direct3DRM. В полноэкранной версии функция OnCreate() инициализирует Direct3D, DirectDraw, поверхности DirectDraw и устройство Direct3D. Функция также создает любые, специфичные для конкретного приложения элементы. Код функции OnCreate() приведен в листинге10.2.

Листинг 10.2. Функция OnCreate()

int RMWin::OnCreate(LPCREATESTRUCT) { ShowCursor(FALSE); Direct3DRMCreate(&d3drm); DirectDrawCreate(0, &ddraw, 0); ddraw->SetCooperativeLevel(GetSafeHwnd(), DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX); InitDisplayMode(); InitMainSurfaces(); InstallPalette(); CreateDevice(); d3drm->CreateFrame(0, &scene); CreateScene(); return 0; }

Сначала используется функция Win32 ShowCursor(), чтобы скрыть указатель мыши. Мышь можно использовать в полноэкранном режиме, но не столь надежно, как в оконном. Некоторые видеорежимы (особенно видеорежимы Mode X) искажают вид указателя мыши.

Затем для инициализации Direct3D вызывается функция Direct3DRMCreate(), а для инициализации DirectDraw вызывается функция DirectDrawCreate(). Сразу после инициализации DirectDraw вызывается функция SetCooperativeLevel() для включения полноэкранного монопольного режима. Константа DDSCL_ALLOWMODEX добавлена, чтобы разрешить использование любых поддерживаемых видеорежимов Mode X.

Далее расположен вызов функции InitDisplayMode(). Эта функция создает список поддерживаемых видеорежимов и выбирает видеорежим, включаемый при запуске приложения.

Функция InitMainSurfaces() создает первичную и вторичную поверхности вместе с Z-буфером. Эти три поверхности (Z-буфер это поверхность специального типа) всегда имеют одинаковые высоту и ширину. Эти параметры определяются текущим видеорежимом, поэтому важно, чтобы требуемый видеорежим был активирован до создания поверхностей.

Функция InstallPalette() используется для создания палитры и ее присоединения к первичной и вторичной поверхностям. Она извлекает палитру из файла BMP. Имя файла BMP должно быть сначала получено функцией UsePalette().

Затем вызывается функция CreateDevice() для создания и настройки устройства Direct3D.

После выполнения всех описанных действий создается корневой фрейм (используется указатель scene), и вызывается функция CreateScene(). Вы должны помнить функцию CreateScene() на которой сосредотачивалось наше изучение в предыдущих главах.



Функция Render()


Функция Render() — это последняя (и простейшая) функция класса RMWin, которую мы рассмотрим. Функция Render() объявлена, но не определена:

virtual void Render() = 0;

Render() — это чисто виртуальная функция. Это означает, что функция Render() должна быть переопределена в классах, производных от RMWin. Функция Render() объявлена таким способом чтобы гарантировать, что производные от RMWin классы будут осуществлять обновление экрана приложения. Помимо прочего, функция Render() должна выполнять переключение страниц.




Функция Render()


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

Листинг 10.14. Функция Render()

void FullScreenWin::Render() { if (primsurf->IsLost() == DDERR_SURFACELOST) { TRACE("Restoring primsurf...\n"); primsurf->Restore(); } if (menusurf->IsLost() == DDERR_SURFACELOST) { TRACE("Restoring menusurf...\n"); menusurf->Restore(); UpdateMenuSurface(); } if (fpssurf->IsLost() == DDERR_SURFACELOST) { TRACE("Restoring fpssurf...\n"); fpssurf->Restore(); } DDBLTFX bltfx; memset(&bltfx, 0, sizeof(bltfx)); bltfx.dwSize = sizeof(bltfx); bltfx.dwFillColor = 0; backsurf->Blt(0, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &bltfx); scene->Move(D3DVALUE(1.0)); viewport->Clear(); viewport->Render(scene); device->Update(); UpdateFPSSurface(); if (displayfps) { DWORD w, h, d; GetCurDisplayModeDims(w, h, d); backsurf->BltFast(w - fpsrect.right, h - fpsrect.bottom, fpssurf, &fpsrect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); } backsurf->BltFast(0, 0, menusurf, &menurect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); primsurf->Flip(0, DDFLIP_WAIT); }

В первой части функции производится проверка потери поверхностей. Потеря поверхностей может произойти, если память, используемая поверхностью, потребуется Windows для других целей. Теряется только память поверхности, а не сама поверхность. Обычно потеря поверхностей происходит, когда пользователь нажимает комбинацию клавиш ALT+TAB для переключения на другую программу.

Функция IsLost() интерфейса DirectDrawSurface возвращает TRUE если память поверхности была потеряна. Восстановить память поверхности очень просто — достаточно вызвать функцию Restore() интерфейса DirectDrawSurface. Это возвратит утраченную память поверхности, но не восстановит содержимое памяти. Обратите внимание, как восстанавливается поверхность меню видеорежимов:

if (menusurf->IsLost() == DDERR_SURFACELOST) { TRACE("Restoring menusurf...\n"); menusurf->Restore(); UpdateMenuSurface(); }

Если произошла потеря поверхности, вызывается функция Restore(). Она выполняет восстановление памяти поверхности, а вызов функции UpdateMenuSurface() необходим для восстановления содержимого поверхности.

Затем стирается содержимое поверхности backsurf:

DDBLTFX bltfx; memset(&bltfx, 0, sizeof(bltfx)); bltfx.dwSize = sizeof(bltfx); bltfx.dwFillColor = 0; backsurf->Blt(0, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &bltfx);

Аналогичный код используется в функции RMWin::ClearSurface(). Функция Blt() применяется для заливки поверхности указанным цветом. Поверхность заполняется нулевыми байтами, но для данной поверхности не был назначен цветовой ключ, так что все пиксели поверхности остаются нулевыми.

Теперь можно нарисовать часть сцены, относящуюся к Direct3D:

scene->Move(D3DVALUE(1.0)); viewport->Clear(); viewport->Render(scene); device->Update();

Функция Move() интерфейса Direct3DRMFrame обновляет внутренние значения иерархии фреймов и выполняет вызов любых функций обратного вызова, выполняющих перемещение входящих в иерархию фреймов. Вызов функций интерфейса Direct3DRMViewport (Clear() и Render()) очищает содержимое порта просмотра (это не оказывает никакого действия на наши поверхности) и вычисляет новое выводимое изображение. Функция Update() интерфейса Direct3DRMDevice копирует сформированное изображение на поверхность backsurf. Это может ускользнуть от вашего взора, поскольку поверхность backsurf не упоминается в данном фрагменте кода. Однако, следует вспомнить, что поверхность backsurf была использована при создании устройства Direct3D. Эта связь объясняет, почему сформированное изображение передается функцией Update() непосредственно на поверхность backsurf.

Затем поверх сформированного Direct3D изображения выводится поверхность для вывода FPS:

UpdateFPSSurface(); if (displayfps) { DWORD w, h, d; GetCurDisplayModeDims(w, h, d); backsurf->BltFast(w - fpsrect.right, h - fpsrect.bottom, fpssurf, &fpsrect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); }

Вызов функции UpdateFPSSurface() обновляет содержимое поверхности fpssurf. Затем, если значение логической переменной displayfps равно TRUE, содержимое поверхности копируется на поверхность backsurf функцией BltFast() (BltFast() — это оптимизированная версия функции Blt()).

Поверхность fpssurf размещается в нижнем правом углу экрана, поэтому при вычислении местоположения копируемой поверхности во вторичном буфере используются размеры экрана.

Потом на поверхность backsurf копируется поверхность меню видеорежимов:

backsurf->BltFast(0, 0, menusurf, &menurect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

Этот код очень простой, поскольку поверхность меню видеорежимов всегда видна и располагается в левом верхнем углу экрана.

В конце функции содержимое поверхности backsurf перемещается на первичную поверхность операцией переключения страниц:

primsurf->Flip(0, DDFLIP_WAIT);

Этот вызов функции делает видимым содержимое поверхности backsurf.



Функция UpdateFPSSurface()


Функция UpdateFPSSurface() вызывается при каждом обновлении экрана. Ее код показан в листинге10.13.

Листинг 10.13. Функция UpdateFPSSurface()

BOOL FullScreenWin::UpdateFPSSurface() { static const long interval = 100; static long framecount; 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, "FPS: %d", fps); HDC hdc; fpssurf->GetDC(&hdc); SelectObject(hdc, smallfont); SetTextColor(hdc, RGB(255,255,255)); SetBkColor(hdc, RGB(0,0,0)); SetBkMode(hdc, OPAQUE); ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &fpsrect, buf, len, 0); fpssurf->ReleaseDC(hdc); displayfps = TRUE; framecount = 0; } return TRUE; }

Функция использует статическую переменную (framecount) для подсчета количества обновлений экрана, или кадров. Когда счетчик кадров достигнет 100 (это значение выбрано произвольно), будет вычислено и выведено на экран значение FPS для последних 100 кадров.

Для определения времени, прошедшего с предыдущего вычисления FPS используется быстродействующая мультимедийная функция timeGetTime(). Затем создается строка, содержащая вычисленное значение FPS, которая выводится на поверхность fpssurf. Сразу после обновления поверхности счетчик framecount обнуляется.



Функция UpdateMenuSurface()


Задача функции UpdateMenuSurface() (код которой приведен в листинге10.11) — инициализировать содержимое поверхности меню видеорежимов.

Листинг 10.11. Функция UpdateMenuSurface()

BOOL FullScreenWin::UpdateMenuSurface() { char buf[80]; int len; RECT rect; ClearSurface(menusurf, 0); HDC hdc; menusurf->GetDC(&hdc); SelectObject(hdc, largefont); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, textshadow); ExtTextOut(hdc, 1, 1, 0, 0, headertext, strlen(headertext), 0); SetTextColor(hdc, textcolor); ExtTextOut(hdc, 0, 0, 0, 0, headertext, strlen(headertext), 0); SelectObject(hdc, smallfont); int nmodes = GetNumDisplayModes(); if (nmodes > maxmodes) nmodes = maxmodes; int rows = nmodes / menucols; if (nmodes % menucols) rows++; for (int i = 0; i < nmodes; i++) { rect.left = (i / rows) * colwidth; rect.top = (i % rows) * rowheight + reservedspace; rect.right = rect.left + colwidth; rect.bottom = rect.top + rowheight; DWORD w,h,d; GetDisplayModeDims(i, w, h, d); len = sprintf(buf, "%dx%dx%d", w, h, d); SetTextColor(hdc, textshadow); ExtTextOut(hdc, rect.left + 1, rect.top + 1, 0, &rect, buf, len, 0); if (i == selectmode) SetTextColor(hdc, highlightcolor); else SetTextColor(hdc, textcolor); ExtTextOut(hdc, rect.left, rect.top, 0, &rect, buf, len, 0); } rect.left = 0; rect.right = 319; rect.top = 179; rect.bottom = 199; len=sprintf(buf, "[Arrows] [Enter] [W] [F] [G] [Escape]"); SetTextColor(hdc, textshadow); ExtTextOut(hdc, 1, 180, 0, &rect, buf, len, 0); SetTextColor(hdc, textcolor); ExtTextOut(hdc, 0, 179, 0, &rect, buf, len, 0); menusurf->ReleaseDC(hdc); return TRUE; }

Сперва функция UpdateMenuSurface() очищает всю поверхность, заполняя отведенную для нее память нулями. Это делается с помощью функции RMWin::ClearSurface(). Ноль, передаваемый во втором аргументе ClearSurface(), задает желаемое значение пикселей поверхности. Поскольку ноль задан в качестве цветового ключа данной поверхности, вся поверхность станет прозрачной.

Затем вызывается функция GetDC() интерфейса DirectDrawSurface, чтобы получить контекст устройства Windows для поверхности. Полученный в результате HDC (дескриптор контекста устройства) позволяет нам пользоваться функциями контекста устройства Windows. В нашем случае мы воспользуемся дескриптором контекста устройства для вывода текста на поверхность.

Большая часть кода функции UpdateMenuSurface() осуществляет вывод текста на поверхность menusurf. Сначала используется функция Win32 ExtTextOut() для отображения заголовка приложения. Затем создаются и выводятся строки для каждого обнаруженного видеорежима. И, наконец, в нижней части поверхности выводится список клавиш, на которые реагирует программа.

Обратите внимание, что текст выводится дважды, разным цветом и с небольшим сдвигом. Благодаря этому создается эффект тени.

Перед возвратом из функции UpdateMenuSurface() вызывается функция ReleaseDC() интерфейса DirectDrawSurface. Это очень важно, поскольку часть механизмов управления Windows отключается между вызовами функций GetDC() и ReleaseDC(). Если вы забудете освободить контекст устройства для поверхности DirectDraw, то это приведет к впечатляющему краху системы (поверьте мне — я знаю!).



Полноэкранные приложения


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

Полноэкранные приложения — это программы, которые игнорируют рабочий стол Windows и все устанавливаемые им правила. Полноэкранные приложения получают полный контроль над видеокартой и могут использовать любые поддерживаемые видеокартой видеорежимы.

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

Возможно наибольшим отличием является то, что в полноэкранном приложении мы должны сами реализовать обновление экрана и работу с палитрой. В приложениях Direct3D, которые работают в оконном режиме, мы не думали об этих задачах. Хорошей новостью является то, что палитра необходима только приложениям, работающим в 8-разрядных видеорежимах (16-, 24- и 32-разрядные видеорежимы обходятся без палитр).

Другое отличие заключается в том, что Windows не «понимает» полноэкранные приложения. Обычно обновление экрана Windows выполняет интерфейс графических устройств Windows (GDI). GDI ничего не знает о полноэкранных приложениях и предполагает, что всегда сам управляет экраном. Это может вызвать проблемы, поскольку если GDI будет использоваться для вывода меню или окон в полноэкранном режиме, он будет делать это не учитывая текущий видеорежим и палитру. Некоторые полноэкранные приложения используют GDI для отображения меню, несмотря на его плохую поддержку работы в полноэкранном режиме, однако наилучший способ — вообще отказаться от использования GDI в полноэкранном режиме. Чтобы сделать GDI неактивным можно разместить поверх всех остальных окон развернутое на весь экран пустое окно без меню.

Наше обсуждение мы начнем с краткого обзора DirectDraw. Затем мы модифицируем класс RMWin для поддержки полноэкранных операций. И в заключение мы используем новую версию класса RMWin при создании приложения FullScreen.



Интерфейс DirectDraw


DirectDraw, подобно Direct3D, предоставляет большую часть своих функциональных возможностей через главный интерфейс. Для абстрактного режима Direct3D, главным является интерфейс Direct3DRM. Главный интерфейс DirectDraw называется DirectDraw.

Интерфейс DirectDraw создается функцией DirectDrawCreate(), вызов которой выглядит следующим образом:

LPDIRECTDRAW ddraw; DirectDrawCreate(0, &ddraw, 0);



Класс FullScreenWin


Функциональность приложения FullScreen сосредоточена в классе FullScreenWin. В качестве базового класса для FullScreenWin используется класс RMWin, что можно увидеть в листинге 10.8.

Листинг 10.8. Класс FullScreenWin

class FullScreenWin : public RMWin { public: FullScreenWin(); protected: //{{AFX_MSG(FullScreenWin) afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: void OnRenderWireframe(); void OnRenderFlat(); void OnRenderGouraud(); BOOL CreateScene(); void Render(); static void UpdateAnimation(LPDIRECT3DRMFRAME, void*, D3DVALUE); BOOL CreateMenuSurface(); BOOL UpdateMenuSurface(); BOOL CreateFPSSurface(); BOOL UpdateFPSSurface(); private: LPDIRECT3DRMMESHBUILDER meshbuilder; static LPDIRECT3DRMANIMATION animation; HFONT smallfont, largefont; LPDIRECTDRAWSURFACE menusurf; RECT menurect; int selectmode; LPDIRECTDRAWSURFACE fpssurf; RECT fpsrect; BOOL displayfps; };

Единственной открытой функцией класса является конструктор, который отвечает за инициализацию динамических переменных класса.

Также объявлен единственный обработчик сообщений: OnKeyDown(). MFC вызывает эту функцию каждый раз, когда нажимается клавиша на клавиатуре. Мы воспользуемся этой функцией для реализации меню приложения.

Обратите внимание, что обычные функции реализации меню Render отсутствуют в секции MFC afx_msg. Вместо этого, они объявлены как три обычные закрытые функции класса. Это связано с тем, что мы не используем стандартное меню Windows. Нам придется самим вызывать эти функции, вместо того чтобы позволить MFC выполнить эту работу за нас.

Затем объявлена функция CreateScene(). Как и в приложениях из предыдущих глав, здесь функция CreateScene() отвечает за создание видимых объектов приложения.

Далее объявлена функция Render(). Вспомните, что в классе RMWin функция Render() объявлена как чисто виртуальная функция. Поэтому мы должны предоставить версию этой функции в классе FullScreenWin. Данная функция используется для визуализации и отображения выходных данных приложения.

Также объявлена статическая функция обратного вызова UpdateAnimation(). Она применяется для обновления анимационной последовательности приложения.

Следующие четыре функции, объявленные в классе FullScreenWin заслуживают более подробного обсуждения:

BOOL CreateMenuSurface(); BOOL UpdateMenuSurface(); BOOL CreateFPSSurface(); BOOL UpdateFPSSurface();

Когда вы запускаете приложение FullScreen в верхнем левом углу экрана появляется список доступных видеорежимов, а примерно через две секунды, в нижнем правом углу появляется счетчик частоты кадров (FPS).

Каждое из этих изображений отображается с использованием поверхностей, создаваемых и поддерживаемых классом FullScreenWin. Функции CreateMenuSurface() и UpdateMenuSurface() создают список (или меню) видеорежимов и управляют им. Функции CreateFPSSurface() и UpdateFPSSurface() создают и управляют счетчиком FPS.

В оставшейся части класса содержатся объявления переменных. Здесь объявлены указатели на интерфейсы Direct3DRMMeshBuilder и Direct3DRMAnimation. Следом расположены объявления двух экземпляров HFONT. Тип HFONT — это дескриптор шрифта Windows. Мы будем использовать эти дескрипторы для вывода текста меню видеорежимов и счетчика частоты кадров.

Следом объявлены переменные, которые будут использоваться для реализации меню видеорежимов:

LPDIRECTDRAWSURFACE menusurf; RECT menurect; int selectmode;

Указатель menusurf используется для управления поверхностью, содержащей меню видеорежимов. Структура menurect хранит размеры поверхности меню. Целочисленная переменная selectmode применяется для отслеживания выделенного в данный момент видеорежима. Не следует путать выделенный видеорежим с активным видеорежимом. Соответствующий видеорежиму пункт меню может быть выделен с помощью клавиш управления курсором, однако выделенный видеорежим не станет активным, пока пользователь не нажмет клавишу ENTER.

Оставшиеся переменные предназначены для вывода счетчика FPS:

LPDIRECTDRAWSURFACE fpssurf; RECT fpsrect; BOOL displayfps;

Указатель fpssurf используется для обновления и отображения поверхности счетчика FPS. Структура fpsrect хранит размеры поверхности fpssurf. Логическая переменная displayfps применяется для реализации задержки отображения счетчика FPS при каждой смене видеорежима. Это сделано по той причине, что начальные значения счетчика FPS обычно неправильны. Поэтому значение счетчика FPS отображается только после того, как скорость работы приложения стабилизируется.



Класс RMWin


Степень модификации станет ясной, если вы сравните определение класса RMWin из главы 4 с полноэкранной версией класса RMWin, приведенной в листинге 10.1.

Листинг 10.1. Класс RMWin

class RMWin : public CFrameWnd { public: RMWin(); RMWin(int w, int h); BOOL Create(const CString& sTitle, int icon, int menu); void SetColorModel(D3DCOLORMODEL cm) { colormodel = cm; } virtual void Render() = 0; protected: int GetNumDisplayModes() {return totaldisplaymodes; } BOOL ActivateDisplayMode(int index); int GetCurDisplayMode() { return curdisplaymode; } BOOL GetDisplayModeDims(int index, DWORD& w, DWORD& h, DWORD& d ); BOOL GetCurDisplayModeDims(DWORD& w, DWORD& h, DWORD& d); static void CheckResult(HRESULT); static void CheckDirectDrawResult(HRESULT); virtual void OnIdle(LONG) { } static int GetMouseX() { return mousex; } static int GetMouseY() { return mousey; } D3DVALUE ScaleMesh(LPDIRECT3DRMMESHBUILDER, D3DVALUE); void UsePalette(CString filename) { palettefile = filename; } LPDIRECTDRAWSURFACE CreateSurface(DWORD w, DWORD h); BOOL ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD clr); void SaveSurface(LPDIRECTDRAWSURFACE surf, int number); protected: //{{AFX_MSG(RMWin) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnMouseMove(UINT state, CPoint point); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: BOOL InitMainSurfaces(); BOOL InitDisplayMode(); BOOL ActivateDisplayMode(DWORD, DWORD, DWORD); void Initvars(); virtual BOOL CreateScene() = 0; BOOL CreateDevice(); GUID* GetGUID(); BOOL InstallPalette(); static HRESULT WINAPI DisplayModeAvailable(LPDDSURFACEDESC, LPVOID); static int CompareModes(const void *arg1, const void *arg2); protected: LPDIRECTDRAW ddraw; LPDIRECTDRAWSURFACE primsurf; LPDIRECTDRAWSURFACE backsurf; LPDIRECTDRAWSURFACE zbufsurf; LPDIRECTDRAWPALETTE palette; static LPDIRECT3DRM d3drm; static LPDIRECT3DRMFRAME scene; static LPDIRECT3DRMFRAME camera; static LPDIRECT3DRMDEVICE device; static LPDIRECT3DRMVIEWPORT viewport; private: static DWORD modewidth, modeheight, modedepth; D3DCOLORMODEL colormodel; CRect winrect; LPDIRECTDRAWCLIPPER clipper; static int mousex; static int mousey; static UINT mousestate; static int totaldisplaymodes; static videomode displaymode[MAXDISPLAYMODES]; static int curdisplaymode; CString palettefile; };

Давайте взглянем, чем это определение класса отличается от оконной версии класса RMWin. Сначала мы посмотрим какие функции, присутствовавшие в оконной версии, отсутствуют здесь. Затем мы рассмотрим функции, добавленные для поддержки полноэкранных возможностей.



Модификация класса RMWin


В главе4 мы обсуждали стратегию построения структуры классов, используемой для написания рассматриваемых в этой книге приложений. Наша стратегия состояла в создании двух унаследованных от MFC классов, которые предоставляли поддержку Direct3D. Мы назвали эти классы RMWin и RMApp. В каждом демонстрационном приложении были еще два класса: один производный от RMWin, а другой производный от RMApp. Эти специфичные для конкретного приложения классы наращивали и модифицировали функциональность базовых классов. На рис. 10.1 показано дерево наследования классов, полученное нами в результате этой работы.



Рис. 10.1. Используемая в книге иерархия классов


При переходе к полноэкранным приложениям мы сохраним показанную на рисунке архитектуру. Таким образом, любые новые возможности, которые мы добавим в классы RMWin и RMApp, будут автоматически унаследованы производными классами. Такой подход упрощает написание последующих приложений.

Поскольку внутреннее устройство полноэкранных приложений отличается от оконных приложений, классы Direct3D (в особенности RMWin) должны быть значительно модифицированы.



Палитры


Для 8-разрядных видеорежимов необходима палитра. Для оконных приложений Direct3D автоматически создает и устанавливает палитру. В полноэкранных приложениях предоставить палитру должна наша программа.

СОВЕТ Захват палитры. Простейший способ получения палитры для вашего приложения — создание оконной версии приложения и последующее использование программы захвата изображений (такой, как Paint Shop Pro) для захвата содержимого окна. Полученный в результате файл будет содержать цвета, необходимые для отображения полноэкранной версии. В этой главе мы узнаем, как извлечь палитру из файлов BMP.

Палитры представлены интерфейсом DirectDrawPalette и создаются с помощью функции CreatePalette() интерфейса DirectDraw.




Переключение страниц


Одной из важнейших возможностей DirectDraw является переключение страниц. Переключением страниц называется способ анимации при котором изображение подготавливается во внеэкранном буфере, а затем отображается на экране. Этот метод полезен по двум причинам. Во-первых, подготовка изображения в скрытом буфере позволяет устранить мерцание изображения во время анимации, поскольку перед тем как стать видимым, новый кадр уже полностью собран. Вторая причина— скорость. Переключение страниц осуществляется аппаратурой видеокарты, поэтому обновление экрана происходит практически мгновенно.

Для переключения страниц необходимы два буфера — первичный (primary buffer) и вторичный (back buffer). Первичный буфер содержит данные, которые в текущий момент времени отображаются на экране. Вторичный буфер содержит изображение, которое может быть перемещено в первичный буфер за одну операцию. Позже мы создадим и первичный, и вторичный буфер. Мы будем копировать трехмерные и двухмерные изображения во вторичный буфер, а затем выполнять переключение страниц, чтобы сделать новое изображение видимым.



Поддержка палитры


Работающие в 8-разрядных видеорежимах полноэкранные приложения требуют, чтобы к каждой поверхности была присоединена созданная программой палитра. Класс RMWin получает палитры из файлов BMP. Для этой цели предназначена функция UsePalette().

Внутри класса RMWin для извлечения данных палитры из файла BMP и создания палитры DirectDraw используется функция InstallPalette(). Затем новая палитра присоединяется к поверхностям.



Поверхности


В DirectDraw поверхностью (surface) называется область памяти, предназначенная для хранения изображений. Используемая поверхностью память может быть расположена как на видеокарте, так и в системной памяти. Однако отображаться на экране могут только те поверхности, которые размещены в памяти видеокарты.

Поверхности представлены интерфейсом DirectDrawSurface и могут быть созданы функцией CreateSurface() интерфейса DirectDraw. Функции CreateSurface() передаются аргументы, которые задают тип, размер и возможности создаваемой поверхности.

Существует три основных типа поверхностей: переключаемые поверхности (flipping surfaces), внеэкранные поверхности (off-screen surfaces) и Z-буферы (Z-buffers). Переключаемые поверхности используются для выполнения переключения страниц. Первичный и вторичный буфер в нашем коде будут представлять собой переключаемые поверхности. Внеэкранные поверхности используются для хранения изображений, таких как фоновые картинки и спрайты, и различных манипуляций с ними. Z-буферы мы обсудим в следующем разделе.



FullScreen


В оставшейся части главы мы изучим код приложения FullScreen. FullScreen— это приложение Direct3D, работающее в полноэкранном режиме. Приложение использует полноэкранную версию класса RMWin, чтобы получить возможность переключать видеорежимы и выполнять переключение страниц. Вид экрана приложения показан на рис. 10.2.



Рис. 10.2. Приложение FullScreen


Приложение FullScreen осуществляет анимацию знакомой нам сетки завитка в полноэкранном режиме, используя интерфейс Direct3DRMAnimation. Более интересен тот факт, что приложение отображает список всех обнаруженных видеорежимов и позволяет активировать любой из них. Для выбора видеорежима используются клавиши управления курсором, выбранный режим включается при нажатии на клавишу ENTER.

Когда вы запускаете приложение FullScreen, на экране отображаются все видеорежимы, поддерживаемые вашей видеокартой. Меню, изображенное на рис. 10.2 показывает видеорежимы, поддерживаемые видеокартой ATI Mach 64.

СОВЕТ Ограничения, накладываемые монитором. Чтобы видеорежим работал правильно, его должны поддерживать и видеокарта, и монитор. Приложение FullScreen отображает видеорежиы, поддерживаемые видеокартой. Они могут поддерживаться, а могут и не поддерживаться монитором. Если вы выбрали видеорежим, и экран остается темным более 10 секунд, нажмите клавишу ESC, чтобы завершить работу приложения и вернуться к рабочему столу Windows.

Приложение также отображает индикатор скорости работы — счетчик частоты кадров (FPS).

Приложение FullScreen демонстрирует применение следующих технологий:

Создание полноэкранных приложений Direct3D. Переключение страниц. Добавление двумерных поверхностей к трехмерной сцене. Использование функций Win32 для вывода текста на поверхности DirectDraw. Вычисление частоты кадров (FPS). Альтернатива традиционному меню Windows.



Удаление ненужных функций


Первое отличие заключается в том, что четыре функции, присутствующие в оконной версии, отсутствуют в новом определении класса. Это обусловлено тем, что перечисленные ниже функции не требуются при выполнении полноэкранных операций:

OnActivate() OnPaint() OnSize() OnEraseBkgnd()

В оконных приложениях эти четыре функции служили обработчиками событий. Функция OnActivate() вызывалась MFC когда приложение получало или теряло фокус. Мы использовали функцию OnActivate() чтобы оповещать Direct3D о поступлении сообщения WM_ACTIVATE с помощью функции HandleActivate() интерфейса Direct3DRMWinDevice. В полноэкранных приложениях подобное уведомление не требуется.

То же самое верно и для функции OnPaint(). В оконных приложениях мы использовали ее для вызова функции HandlePaint() интерфейса Direct3DRMWinDevice, что разрешало Direct3D выполнять обновление экрана. Поскольку обновление экрана мы теперь будем выполнять самостоятельно, функция OnPaint() нам больше не требуется.

Хотя функции OnActivate() и OnPaint() удалены, сообщения WM_ACTIVATE и WM_PAINT по-прежнему поступают и обрабатываются нашим приложением. По другому обстоит дело с сообщением WM_SIZE. Функция OnSize() была удалена потому, что размер окна приложения теперь не может быть изменен. Это полноэкранное приложение и функция изменения размера окна в нем не имеет смысла.

И, наконец, функция OnEraseBkgnd() была удалена потому, что ее задачей является очистка фона окна. Хотя наше приложение и создает окно, очистка его содержимого не требуется. Окно присутствует в основном для спокойствия GDI.



Уровни кооперации


Степень контроля DirectDraw над видеокартой может быть настроена с помощью функции SetCooperativeLevel(). Уровень кооперации (cooperative level) определяет, в какой мере DirectDraw будет сотрудничать с другими приложениями. Для наших целей мы воспользуемся функцией SetCooperativeLevel() чтобы установить монопольный полноэкранный режим.



Видеорежимы


Интерфейс DirectDraw позволяет нам определять и активировать видеорежимы. Для определения поддерживаемых видеорежимов предназначена функция EnumDisplayModes(). Найденный видеорежим может быть включен с помощью функции SetDisplayMode(). Функция RestoreDisplayMode() используется для возвращения видеокарты к тому видеорежиму, который был установлен до запуска нашего приложения. Позднее, при написании приложения FullScreen, мы воспользуемся этими функциями, чтобы наша программа могла включать любой поддерживаемый видеорежим.



Z-буфферизация


Одно из преимуществ использования Direct3D в оконной среде— автоматическое создание Z-буферов. В полноэкранных приложениях мы должны создать Z-буфер сами.

Z-буфер — это поверхность DirectDraw, обладающая возможностью Z-буфферизации. Подобно обычной поверхности, Z-буфер создается функцией CreateSurface() интерфейса DirectDraw. Как только Z-буфер создан и установлен, Direct3D может использовать его не требуя дальнейшего вмешательства.



Я надеюсь, что вы узнали


Вот и все — это последняя глава книги. Я надеюсь, что вы узнали все, что хотели, а может быть и немного больше. Удачи!