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

Инициализация приложения


Наше знакомство с программой Switch начинается с конструктора switchWin, внутри которого происходит первоначальная инициализация переменных класса. Не следует путать эту инициализацию с той, что выполняется функцией CreateCustomSurfaces(), потому что в отличие конструктора CreateCustomSurfaces() вызывается при каждой смене видеорежима. Конструктор выглядит так:


SwitchWin::SwitchWin() { bmpsurf=0; x=y=0; xinc=8; yinc=1; menusurf=0; fpssurf=0;

vlargefont = 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" ); }


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

После того как объект SwitchWin будет создан, DirectDrawWin вызывает функции SelectDriver() и SelectInitialDisplayMode(). Поскольку в программе Switch обе функции ведут себя стандартным образом (как описано в главе 3), мы не будем их рассматривать.

Затем класс DirectDrawWin вызывает функцию SwitchWin::CreateCustomSurfaces(), в которой подготавливает три поверхности, используемые программой Switch:


BOOL SwitchWin::CreateCustomSurfaces() { int displaydepth=GetCurDisplayDepth(); CString filename; if (displaydepth==8) filename="tri08.bmp"; else filename="tri24.bmp";

bmpsurf=CreateSurface( filename, TRUE ); if (bmpsurf==0) { TRACE("surface creation failed\n"); return FALSE; }

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

CreateFPSSurface();

return TRUE; }


Содержимое одной из этих трех поверхностей определяется BMP-файлом.


Как упоминалось выше, перед инициализацией DirectDraw программа SuperSwitch выводит в функции SuperSwitchWin::OnCreate() диалоговое окно. После вывода диалогового окна функция вызывает версию OnCreate() класса DirectDrawWin. Код функции SuperSwitchWin::OnCreate() выглядит так:


int SuperSwitchWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { IntroDialog introdialog; if (introdialog.DoModal()!=IDOK) return -1;

include_refresh=introdialog.include_refresh;

if (DirectDrawWin::OnCreate(lpCreateStruct)==-1) return -1;

if (include_refresh) ddraw2->EnumDisplayModes( DDEDM_REFRESHRATES, 0, this, StoreModeInfo );

return 0; }


Сначала мы создаем объект класса IntroDialog — этот класс-оболочка был сгенерирован ClassWizard. Диалоговое окно отображается функцией CDialog::DoModal(), которая возвращает код IDOK в случае нажатия пользователем кнопки OK. Если пользователь закрывает диалоговое окно другим способом (например, нажимая кнопку Cancel), функция OnCreate() возвращает код –1, что для MFC является признаком завершения приложения. Если была нажата кнопка OK, переменной include_refresh присваивается значение в зависимости от состояния флажка в диалоговом окне.

Теперь мы вызываем версию OnCreate() класса DirectDrawWin, где и происходит инициализация DirectDraw. Функция составляет список видеорежимов, активизирует исходный режим и создает поверхности приложения. Если вызов функции OnCreate() завершается неудачей, мы завершаем приложение, возвращая код –1.

Следующий шаг - повторное составление списка видеорежимов. На этот раз при вызове функции EnumDisplayModes() в первом аргументе передается флаг DDEDM_REFRESHRATES, согласно которому каждый видеорежим должен быть включен в список по одному разу для каждой поддерживаемой частоты. В результате мы сможем построить список частот для каждого видеорежима. Четвертый аргумент EnumDisplayModes() — функция косвенного вызова StoreModeInfo(), которая выглядит так:


HRESULT WINAPI SuperSwitchWin::StoreModeInfo( LPDDSURFACEDESC desc, LPVOID p) { DWORD w=desc->dwWidth; DWORD h=desc->dwHeight; DWORD d=desc->ddpfPixelFormat.dwRGBBitCount; DWORD r=desc->dwRefreshRate;

SuperSwitchWin* win=(SuperSwitchWin*)p; int index=win->GetDisplayModeIndex( w, h, d ); win->refresh_rates[index].Add(r);

return DDENUMRET_OK; }


Функции StoreModeInfo()> передается указатель на структуру DDSURFACEDESC с описанием очередного видеорежима. В описание входит частота смены кадров (поле dwRefreshRate), а также размеры, по которым определяется индекс режима. Затем этот индекс используется для сохранения частоты видеорежима в массиве.

После выхода из функции OnCreate() класс DirectDrawWin вызывает функцию CreateCustomSurfaces(). По сравнению с программой Switch эта функция не изменилась; она по-прежнему создает три поверхности, потому что новая поверхность (ratemenusurface) создается только в случае необходимости.




Перед тем как инициализировать DirectDraw, класс DirectDrawWin вызывает функцию SelectDriver(), чтобы производные классы могли выбрать драйвер DirectDraw при наличии нескольких вариантов. В программе BmpView мы отказываемся от этой возможности и позволяем выбрать первичный драйвер по умолчанию. Это сделано потому, что для вывода диалоговых окон используется механизм GDI, а GDI может выводить только на первичное видеоустройство (которому соответствует первичный драйвер DirectDraw).

Следующим этапом инициализации приложения является вызов функции SelectInitialDisplayMode(), которую мы обязаны переопределить. Наша версия SelectInitialDisplayMode() выбирает видеорежим с параметрами 640x480x16. Исходный видеорежим не так уж важен, потому что он, скорее всего, будет переопределен пользователем при выборе BMP-файла. Однако функция SelectInitialDisplayMode() (см. листинг 5.6) выполняет две дополнительные задачи.

Листинг 5.6. Функция BmpViewWin::SelectInitialDisplayMode()


int BmpViewWin::SelectInitialDisplayMode() { DisplayModeDescription desc; int i, nummodes=GetNumDisplayModes(); DWORD w,h,d;

for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); desc.w=w; desc.h=h; desc.d=d; desc.desc.Format("%dx%dx%d", w, h, d );

if ( d==8 ) palettemode.Add( desc ); else nonpalettemode.Add( desc ); }

DWORD curdepth=GetDisplayDepth();

for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==640 && h==480 && d==curdepth) return i; }

for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (d==curdepth) return i; }

for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==640 && h==480) return i; }

GetSystemPalette();

return 0; }


Помимо выбора исходного видеорежима функция SelectInitialDisplayMode() используется для подготовки двух массивов: в первом хранятся сведения о палитровых (palettemode), а во втором — о беспалитровых (nonpalettemode) видеорежимах. Мы воспользуемся этими массивами позднее, при отображении диалогового окна.


Наше знакомство с программой Cursor начинается с функции OnCreate(), которая отвечает за инициализацию DirectDraw, DirectInput и потока ввода. Функция OnCreate() приведена в листинге 7.2.

Листинг 7.2. Функция CursorWin::OnCreate()


int CursorWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { HRESULT r=DirectInputCreate( AfxGetInstanceHandle(), DIRECTINPUT_VERSION, &dinput, 0 ); if (r!=DI_OK) { AfxMessageBox("DirectInputCreate() failed"); return -1; }

if (InitMouse()==FALSE) return -1;

if (InitKeyboard()==FALSE) return -1;

if (DirectDrawWin::OnCreate(lpCreateStruct) == -1) return -1;

mousethread->ResumeThread();

return 0; }


Сначала OnCreate() инициализирует DirectInput функцией DirectInputCreate(). Затем мышь и клавиатура инициализируются функциями InitMouse() и InitKeyboard(), после чего вызывается функция DirectDrawWin::OnCreate(). Функция InitMouse(), которую мы рассмотрим чуть ниже, создает поток ввода, доступ к которому осуществляется через указатель mousepointer. Однако поток ввода создается в приостановленном состоянии, чтобы он не пытался преждевременно обращаться к первичной поверхности. Поток будет запущен лишь после инициализации DirectDraw. Приостановленный поток активизируется функцией CWinThread::ResumeThread().

Давайте рассмотрим функцию InitMouse(), чтобы получить общее представление об инициализации мыши и создании потока ввода. Функция InitMouse() приведена в листинге 7.3.

Листинг 7.3. Функция InitMouse()


BOOL CursorWin::InitMouse() { HRESULT r; r = dinput->CreateDevice( GUID_SysMouse, &mouse, 0 ); if (r!=DI_OK) { TRACE("CreateDevice(mouse) failed\n"); return FALSE; }

r = mouse->SetDataFormat( &c_dfDIMouse ); if (r!=DI_OK) { TRACE("mouse->SetDataFormat() failed\n"); return FALSE; }

r = mouse->SetCooperativeLevel( GetSafeHwnd(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ); if (r!=DI_OK) { TRACE("mouse->SetCooperativeLevel() failed\n"); return FALSE; }

DIPROPDWORD property; property.diph.dwSize=sizeof(DIPROPDWORD); property.diph.dwHeaderSize=sizeof(DIPROPHEADER); property.diph.dwObj=0; property.diph.dwHow=DIPH_DEVICE; property.dwData=64;

r = mouse->SetProperty( DIPROP_BUFFERSIZE, &property.diph ); if (r!=DI_OK) { TRACE("mouse->SetProperty() failed (buffersize)\n"); return FALSE; }

mouse_event[mouse_event_index]=new CEvent; mouse_event[quit_event_index]=new CEvent;

r = mouse->SetEventNotification( *mouse_event[mouse_event_index] ); if (r!=DI_OK) { TRACE("mouse->SetEventNotification() failed\n"); return FALSE; } mousethread=AfxBeginThread( (AFX_THREADPROC)MouseThread, this, THREAD_PRIORITY_TIME_CRITICAL, 0, CREATE_SUSPENDED );

return TRUE; }

<


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


int BumperWin::SelectDriver() { int numdrivers=GetNumDrivers(); if (numdrivers==1) return 0; CArray<CString, CString> drivers; for (int i=0;i<numdrivers;i++) { LPSTR desc, name; GetDriverInfo( i, 0, &desc, &name ); drivers.Add(desc); }

DriverDialog dialog; dialog.SetContents( &drivers ); if (dialog.DoModal()!=IDOK) return -1;

return dialog.GetSelection(); }


С помощью класса DriverDialog

программа выводит меню со списком драйверов и использует драйвер, выбранный пользователем. Наши функции проверки столкновений предназначены только для 8-битных поверхностей, поэтому драйверы, не поддерживающие 8-битных видеорежимов (скажем, драйверы 3Dfx), в этой программе не работают. Следовательно, функция SelectInitialDisplayMode() должна правильно реагировать на выбор такого драйвера.

Функция SelectInitialDisplayMode() вызывается после функции SelectDriver(), но перед созданием поверхностей. Функция выглядит так:


int BumperWin::SelectInitialDisplayMode() { DWORD curdepth=GetDisplayDepth(); int i, nummodes=GetNumDisplayModes(); DWORD w,h,d; if (curdepth!=desireddepth) ddraw2->SetDisplayMode( 640, 480, curdepth, 0, 0 );

for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==desiredwidth && h==desiredheight && d==desireddepth) return i; }

ddraw2->RestoreDisplayMode(); ddraw2->Release(), ddraw2=0; AfxMessageBox("Can't find 8-bit mode on this device");

return -1; }


Функция SelectInitialDisplayMode()

ищет конкретный видеорежим 640x480x8. Если этот режим не найден, она выводит сообщение и возвращает –1, говоря тем самым классу DirectDrawWin о том, что приложение следует завершить. Если режим будет найден, функция возвращает его индекс. По этому индексу класс DirectDrawWin узнает о том, какой видеорежим следует активизировать.

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