netlib.narod.ru | < Назад | Оглавление | Далее > |
В главе 4 мы обсуждали стратегию построения структуры классов, используемой для написания рассматриваемых в этой книге приложений. Наша стратегия состояла в создании двух унаследованных от MFC классов, которые предоставляли поддержку Direct3D. Мы назвали эти классы RMWin и RMApp. В каждом демонстрационном приложении были еще два класса: один производный от RMWin, а другой производный от RMApp. Эти специфичные для конкретного приложения классы наращивали и модифицировали функциональность базовых классов. На рис. 10.1 показано дерево наследования классов, полученное нами в результате этой работы.
Рис. 10.1. Используемая в книге иерархия классов
При переходе к полноэкранным приложениям мы сохраним показанную на рисунке архитектуру. Таким образом, любые новые возможности, которые мы добавим в классы RMWin и RMApp, будут автоматически унаследованы производными классами. Такой подход упрощает написание последующих приложений.
Поскольку внутреннее устройство полноэкранных приложений отличается от оконных приложений, классы Direct3D (в особенности 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. Сначала мы посмотрим какие функции, присутствовавшие в оконной версии, отсутствуют здесь. Затем мы рассмотрим функции, добавленные для поддержки полноэкранных возможностей.
Первое отличие заключается в том, что четыре функции, присутствующие в оконной версии, отсутствуют в новом определении класса. Это обусловлено тем, что перечисленные ниже функции не требуются при выполнении полноэкранных операций:
В оконных приложениях эти четыре функции служили обработчиками событий. Функция OnActivate() вызывалась MFC когда приложение получало или теряло фокус. Мы использовали функцию OnActivate() чтобы оповещать Direct3D о поступлении сообщения WM_ACTIVATE с помощью функции HandleActivate() интерфейса Direct3DRMWinDevice. В полноэкранных приложениях подобное уведомление не требуется.
То же самое верно и для функции OnPaint(). В оконных приложениях мы использовали ее для вызова функции HandlePaint() интерфейса Direct3DRMWinDevice, что разрешало Direct3D выполнять обновление экрана. Поскольку обновление экрана мы теперь будем выполнять самостоятельно, функция OnPaint() нам больше не требуется.
Хотя функции OnActivate() и OnPaint() удалены, сообщения WM_ACTIVATE и WM_PAINT по-прежнему поступают и обрабатываются нашим приложением. По другому обстоит дело с сообщением WM_SIZE. Функция OnSize() была удалена потому, что размер окна приложения теперь не может быть изменен. Это полноэкранное приложение и функция изменения размера окна в нем не имеет смысла.
И, наконец, функция OnEraseBkgnd() была удалена потому, что ее задачей является очистка фона окна. Хотя наше приложение и создает окно, очистка его содержимого не требуется. Окно присутствует в основном для спокойствия GDI.
Полноэкранная версия класса RMWin предоставляет несколько функций, которые облегчают обнаружение и переключение видеорежимов. Вот эти функции:
Внутри класса RMWin собирается список поддерживаемых видеорежимов. Количество элементов в этом списке может быть определено с помощью функции GetNumDisplayModes(). Заданный видеорежим может быть активирован функцией ActivateDisplayMode(). Функция GetCurDisplayMode() возвращает включенный в данный момент видеорежим. Функции GetDisplayModeDims() и GetCurDisplayModeDims() возвращают параметры видеорежима (ширина, высота и глубина цвета экрана).
Класс RMWin создает и обслуживает поверхности DirectDraw, необходимые для полноэкранных операций Direct3D. Как вы увидите позже в этой главе, иногда полезно создавать и отображать дополнительные поверхности. Для этого в класс RMWin были добавлены две функции, относящиеся к работе с поверхностями: CreateSurface() и ClearSurface(). Функция CreateSurface() создает новую поверхность указанного размера. Функция ClearSurface() стирает содержимое существующей поверхности.
Работающие в 8-разрядных видеорежимах полноэкранные приложения требуют, чтобы к каждой поверхности была присоединена созданная программой палитра. Класс RMWin получает палитры из файлов BMP. Для этой цели предназначена функция UsePalette().
Внутри класса RMWin для извлечения данных палитры из файла BMP и создания палитры DirectDraw используется функция InstallPalette(). Затем новая палитра присоединяется к поверхностям.
Функция 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() на которой сосредотачивалось наше изучение в предыдущих главах.
Функция 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() вызывается из функции 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-буфером не требуется.
Класс 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, используемый для открытия файла, выходит из области видимости.
Функция 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 чтобы разрешить использование метода визуализации Гуро (по умолчанию используется плоский метод визуализации).
Класс 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() эти пять вызовов инициализируют приложение в соответствии с начальным видеорежимом. В данном случае эти вызовы изменяют параметры приложения согласно изменению видеорежима.
Функция GetNumDisplayModes() — это защищенная функция, возвращающая количество видеорежимов, обнаруженных функцией InitDisplayMode(). Функция объявлена и определена в определении класса RMWin:
int GetNumDisplayModes() { return totaldisplaymodes; }
Функция GetCurDisplayMode() возвращает индекс включенного в данный момент видеорежима:
int GetCurDisplayMode() { return curdisplaymode; }
Как и в случае с функцией GetNumDisplayModes(), простота функции GetCurDisplayMode() делает ее хорошим кандидатом на определение и объявление внутри определения класса.
Функция 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.
Позднее в этой главе мы воспользуемся данной функцией для получения параметров всех поддерживаемых видеорежимов. Эти параметры будут использованы при создании меню видеорежимов.
Определение функции 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().
Продолжим разговор о вспомогательных функциях и рассмотрим функцию 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.
Подобно функции 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(), что мы хотим к указанной поверхности применить операцию заливки заданным цветом.
Функция Render() — это последняя (и простейшая) функция класса RMWin, которую мы рассмотрим. Функция Render() объявлена, но не определена:
virtual void Render() = 0;
Render() — это чисто виртуальная функция. Это означает, что функция Render() должна быть переопределена в классах, производных от RMWin. Функция Render() объявлена таким способом чтобы гарантировать, что производные от RMWin классы будут осуществлять обновление экрана приложения. Помимо прочего, функция Render() должна выполнять переключение страниц.
netlib.narod.ru | < Назад | Оглавление | Далее > |