• DirectDraw AppWizard
  • Создание приложения Bounce
  • Структура приложения
  • Инициализация DirectDraw
  • Создание поверхностей
  • Графический вывод
  • Оконные приложения
  • Глава 3. За кулисами DirectDraw

    Пора заняться программированием. В этой главе мы создадим несложное приложение DirectDraw и обсудим практически каждую строку его кода. Для создания демонстрационной программы Bounce будет применен мастер DirectDraw AppWizard с прилагаемого CD-ROM. Затем мы изучим структуру классов и подробности реализации этой программы. К моменту завершения у вас появится четкое понимание того, как работают приложения DirectDraw (и, возможно, легкая головная боль).

    DirectDraw AppWizard

    Если вы работали с Visual C++, то вам почти наверняка приходилось пользоваться AppWizard. AppWizard представляет собой специальное приложение, работающее в среде Visual C++. По вашим указаниям он генерирует целый проект вместе со всеми исходными текстами. Хотя сам термин подразумевает, что AppWizard генерируют только приложения, некоторые из них создают библиотеки, управляющие элементы OLE и даже другие AppWizard. Важно понять, что AppWizard позволяет быстро и легко заложить основу для работы над проектом. Несколько AppWizard поставляются вместе с Visual C++, самый известный из них предназначен для создания MFC-приложений.

    Наш нестандартный DirectDraw AppWizard создает приложения DirectDraw. Для установки DirectDraw AppWizard можно воспользоваться инсталляционной программой с CD-ROM или просто скопировать с диска файл AppWizard (с расширением AWX) в каталог с шаблонами Visual C++.

    Создание приложения Bounce

    В Visual C++ Developer Studio вызовите DirectDraw AppWizard. Для этого воспользуйтесь командой File | New, затем выберите Project Workspace — откроется диалоговое окно, в левой части которого перечислены все возможные AppWizard. На рис. 3.1 изображено это окно с выбранным DirectDraw AppWizard.

    Рис. 3.1. Диалоговое окно New Project Workspace


    На рисунке указано имя проекта Bounce. По умолчанию оно определяет имя каталога с проектом, а также используется для присвоения имен ряду элементов проекта, включая некоторые классы C++.

    После того как вы выберете DirectDraw AppWizard, введете имя проекта и нажмете кнопку Create, на экране появится первое диалоговое окно AppWizard (см. рис. 3.2).

    Рис. 3.2. Диалоговое окно с заставкой DirectDraw AppWizard


    Это окно в основном декоративное, но по ряду элементов оно похоже на остальные окна AppWizard. Например, в каждом окне имеются четыре кнопки, изображенные на рис. 3.2. Кнопки Back и Next позволяют перейти к следующему или вернуться к предыдущему окну. При нажатии кнопки Finish появляется диалоговое окно подтверждения, в котором можно просмотреть принятые вами решения. Кнопка Cancel возвращает вас в среду Visual C++. Нажмите кнопку Next, чтобы перейти к следующему окну.

    Во втором окне DirectDraw AppWizard предлагается выбрать тип создаваемого приложения: полноэкранное или оконное. По умолчанию установлен переключатель Full-screen (для полноэкранного приложения). Наше изучение начнется именно с полноэкранных приложений, поэтому оставьте тип, выбранный по умолчанию. Второе окно AppWizard изображено на рис. 3.3.

    Рис. 3.3. Диалоговое окно Application Type


    Снова нажмите кнопку Next, и перед вами появится третье окно AppWizard. В нем указываются исходные параметры приложения. Содержимое этого окна зависит от типа приложения, выбранного на предыдущем шаге. Поскольку ранее было выбрано полноэкранное приложение, в окне Initial Settings можно задать разрешение и глубину пикселей для начального видеорежима. Диалоговое окно Initial Settings изображено на рис. 3.4.

    Рис. 3.4. Диалоговое окно Initial Settings


    По умолчанию выбирается режим 640×80×6. Оставьте значения, принятые по умолчанию, и нажмите кнопку Next.

    В четвертом окне предлагается выбрать содержимое приложения — None или Bitmap. При установке переключателя None генерируемое приложение не делает абсолютно ничего, а при установке Bitmap оно выполняет анимацию растрового изображения. Диалоговое окно Contents изображено на рис. 3.5. По умолчанию устанавливается переключатель Bitmap; именно этот вариант будет использован в приложении Bounce.

    Рис. 3.5. Диалоговое окно Contents


    В пятом и шестом окнах диалога можно переопределить имена классов C++, по умолчанию выбираемые для приложения. Имена классов отображаются в двух текстовых полях; оставьте их без изменений. Диалоговое окно Class Names изображено на рис. 3.6.

    Рис. 3.6. Диалоговое окно Class Names


    Нажмите кнопку Finish. Откроется окно подтверждения, в котором можно просмотреть значения всех выбранных параметров.

    После того как вы подтвердите свое решение, AppWizard создаст новый проект и загрузит его в Developer Studio. Проект готов к компиляции. Если запустить приложение, вы увидите, как по черному экрану бегает растровый объект.

    Рис. 3.7. Диалоговое окно подтверждения

    Структура приложения

    Перед тем как рассматривать структуру проекта Bounce, мы немного поговорим об иерархии классов, использованных для его построения. Так вам будет проще понять код этого приложения, а так как остальные программы на CD-ROM устроены аналогично — то и код всех остальных программ из этой книги.

    Назначение классов

    В реализации программы Bounce используется библиотека MFC, но без традиционной для нее архитектуры «документ/вид». Вместо этого используются классы MFC CWnd и CWinApp. Это позволяет устранить накладные расходы, связанные с архитектурой «документ/вид», и упростить приложение.

    Поддержка DirectDraw сосредоточена в классах DirectDrawWin и DirectDrawApp, производных от CWnd и CWinApp соответственно. В свою очередь от DirectDrawWin и DirectDrawApp порождаются еще два класса. Эти классы (в приложении Bounce они называются BounceWin и BounceApp) обеспечивают функциональность, специфическую для конкретного приложения. На рис. 3.8 изображена иерархия этих шести классов вместе с базовыми классами MFC, используемыми в реализации CWnd и CWinApp.

    Рис. 3.8. Иерархия классов в программе Bounce


    Классы на рисунке соединены стрелками в соответствии с их наследственными связями. Класс CObject, лежащий в основании иерархического дерева, является базовым для всех остальных классов приложения. От него стрелки идут к классам более высокого уровня BounceWin и BounceApp.

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

    Класс CObject является базовым для всех нетривиальных классов MFC. Он обеспечивает самые общие функции классов — операторы присваивания, поддержку сериализации и нестандартные версии операторов new и delete (для отладочных целей). Кроме того, класс CObject содержит виртуальный деструктор. Это гарантирует, что все классы, производные от CObject, будут правильно вести себя во время уничтожения независимо от типа используемого при этом указателя.

    Поддержка схем сообщений (message maps) в MFC реализована в классе CCmdTarget, производном от CObject. Схемами сообщений называются макросы, которые ClassWizard включает в классы, чтобы реализовать обработку сообщений. Поскольку классы нашего приложения являются производными от CCmdTarget, для создания схем сообщений можно использовать ClassWizard.

    Класс CWinThread используется в MFC для инкапсуляции функций программных потоков (execution thread). CWinThread применяется для написания многопоточных приложений, но в нашем случае он обеспечивает работу одного программного потока, необходимого для работы приложения.

    Класс CWinApp, движущая сила всех приложений на базе MFC, использует CWinThread в качестве базового класса и добавляет в него свои функциональные возможности. С помощью поддержки потоков, унаследованной от CWinThread, CWinApp организует получение и доставку сообщений. Именно в нем заключен механизм доставки сообщений, управляющий работой всех Windows-программ. Класс CWinApp является базовым для класса DirectDrawApp, который расширяет поведение CWinApp возможностями, специфическими для DirectDraw. Класс DirectDrawApp управляет поведением приложений DirectDraw, инициализирует класс окна приложения, обновляет содержимое экрана и убирает «мусор» при завершении работы.

    Класс CWnd представляет окна в MFC. CWnd — большой класс; он содержит сотни функций и может выполнять практически любую задачу, связанную с окнами. Мы используем класс CWnd в качестве базового для класса DirectDrawWin, дополняющего функциональные возможности CWnd спецификой DirectDraw. Классы, производные от DirectDrawWin, хорошо подходят для разработки приложений DirectDraw.

    Структура приложения

    Классы DirectDrawWin и DirectDrawApp образуют «каркас» для приложений DirectDraw. Их главная цель — предоставить структурную основу приложения и поддержку основных функций, не скрывая от программиста всех подробностей. Ведь эта книга учит программировать для DirectDraw, а не пользоваться неким набором классов, который в свою очередь использует DirectDraw.

    Итак, классы DirectDrawWin и DirectDrawApp образуют структурную основу приложения и реализуют поддержку его работы. «Структурная основа» означает, что, хотя всю главную работу придется выполнять вам, эти классы решают, когда и как это должно происходить. Например, класс DirectDrawWin позволяет выбрать исходный видеорежим. Для этого класс вызывает написанную вами функцию, которая просматривает список доступных видеорежимов и выбирает один из них. Эта функция вызывается классом DirectDrawWin в нужный момент.

    Структурные классы также поддерживают основные функции приложения. Такая поддержка организуется в виде вспомогательных функций, упрощающих программирование. Например, класс DirectDrawWin содержит функцию для загрузки BMP-файла на поверхность. Это упрощает программу и позволяет эффективно использовать ранее написанный код.

    Тем не менее, если вы собираетесь действительно глубоко изучить DirectDraw, нельзя, чтобы кто-то выполнял всю работу за вас. По этой причине код структурной основы включается в каждый проект; не существует центральной библиотеки классов, используемой во всех программах. Каждый проект, находящийся на CD-ROM или сгенерированный AppWizard, является законченным и вполне самостоятельным; он не зависит ни от чего, кроме MFC и DirectX. Вы сможете модифицировать, дополнять или удалять фрагменты структурного кода так, как сочтете нужным.

    Специализированные классы

    Классы DirectDraw проектировались как основа для классов, реализующих специфические возможности приложения. В случае приложения Bounce эти возможности обеспечиваются классами BounceWin и BounceApp.

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

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

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

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

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

    Давайте перейдем к программному коду. Мы будем рассматривать его в хронологическом порядке, начиная с инициализации приложения. После этого мы займемся выводом и восстановлением потерянных поверхностей, а заодно изучим вспомогательные функции DirectDrawWin. Наконец, наше знакомство с приложением Bounce закончится анализом кода, завершающего работу приложения.

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

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

    BounceApp theapp;

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

    BOOL BounceApp::InitInstance() {

     BounceWin* win=new BounceWin;

     if (!win->Create("High Performance Bounce Demo", IDI_ICON)) {

      AfxMessageBox("Failed to create window");

      return FALSE;

     }

     m_pMainWnd=win;

     return DirectDrawApp::InitInstance();

    }

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

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

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

    BOOL DirectDrawApp::InitInstance() {

     ASSERT(m_pMainWnd);

     m_pMainWnd->ShowWindow(SW_SHOWNORMAL);

     m_pMainWnd->UpdateWindow();

     ShowCursor(FALSE);

     return TRUE;

    }

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

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

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

    BOOL DirectDrawWin::Create(const CString& title,int icon) {

     CString sClassName;

     sClassName = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,   LoadCursor(0, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1),   LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon)));

     return CWnd::CreateEx(WS_EX_TOPMOST, sClassName, title, WS_POPUP, 0, 0, 100, 100, 0, 0);

    }

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

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

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

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

    Фактическое создание окна (вызов функции CreateEx()) заставляет Windows послать нашему приложению сообщение WM_CREATE. Класс DirectDrawWin перехватывает это сообщение в обработчике OnCreate(), созданном ClassWizard (см. листинг 3.1).


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

    int DirectDrawWin::OnCreate(LPCREATESTRUCT) {

     DirectDrawEnumerate(DriverAvailable, this);

     if (totaldrivers==0) {

      AfxMessageBox("No DirectDraw drivers detected");

      return -1;

     }

     int driverindex=SelectDriver();

     if (driverindex<0) {

      TRACE("No DirectDraw driver selected\n");

      return -1;

     } else if (driverindex>totaldrivers-1) {

      AfxMessageBox("Invalid DirectDraw driver selected\n");

      return -1;

     }

     LPDIRECTDRAW ddraw1;

     DirectDrawCreate(driver[driverindex].guid, &ddraw1, 0);

     HRESULT r;

     r=ddraw1->QueryInterface(IID_IDirectDraw2, (void**)&ddraw2);

     if (r!=S_OK) {

      AfxMessageBox("DirectDraw2 interface not supported");

      return -1;

     }

     ddraw1->Release(), ddraw1=0;

     ddraw2->SetCooperativeLevel(GetSafeHwnd(), DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX);

     ddraw2->EnumDisplayModes(0, 0, this, DisplayModeAvailable);

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

     int initmode=SelectInitialDisplayMode();

     if (ActivateDisplayMode(initmode)==FALSE) return -1;

     return 0;

    }

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

    • Получение списка всех драйверов DirectDraw.

    • Выбор драйвера DirectDraw.

    • Инициализация DirectDraw с использованием выбранного драйвера.

    • Получение списка поддерживаемых видеорежимов.

    • Выбор исходного видеорежима.

    • Активизация выбранного видеорежима.

    • Создание поверхностей приложения.

    Все эти этапы рассматриваются в последующих разделах.

    Получение списка драйверов DirectDraw

    Функция DirectDrawEnumerate() предназначена для составления списка доступных драйверов DirectDraw. Чаще всего обнаруживается всего один драйвер DirectDraw — тот, который управляет установленной видеокартой. Тем не менее в некоторых конфигурациях может присутствовать несколько видеоустройств. В таких случаях DirectDrawEnumerate() покажет отдельный драйвер для каждого видеоустройства, поддерживаемого DirectDraw.

    Функция DirectDrawEnumerate() получает два аргумента: указатель на косвенно вызываемую (callback) функцию и указатель на данные, определяемые приложением, которые передаются этой функции при вызове. В нашем случае аргументами являются косвенно вызываемая функция DriverAvailable() и указатель на класс DirectDrawWin (this). Функция DriverAvailable() определяется так:

    BOOL WINAPI DirectDrawWin::DriverAvailable(LPGUID guid, LPSTR desc, LPSTR name, LPVOID p) {

     DirectDrawWin* win=(DirectDrawWin*)p;

     if (win->totaldrivers >= MAXDRIVERS) return DDENUMRET_CANCEL;

     DriverInfo& info=win->driver[win->totaldrivers];

     if (guid)  {

      info.guid=(GUID*)new BYTE[sizeof(GUID)];

      memcpy(info.guid, guid, sizeof(GUID));

     } else info.guid=0;

     info.desc=strdup(desc);

     info.name=strdup(name);

     win->totaldrivers++;

     return DDENUMRET_OK;

    }

    Сначала указатель на данные, определяемые приложением (p), преобразуется в указатель на класс DirectDrawWin (win). Поскольку функция DriverAvailable() объявлена как статическая (косвенно вызываемые функции обязаны быть статическими), на нее в отличие от обычных функций класса не распространяются правила автоматического доступа; соответственно доступ к переменным и функциям класса приходится осуществлять через указатель win.

    DirectDraw вызывает функцию DriverAvailable() один раз для каждого обнаруженного драйвера. При каждом вызове передаются три информационных объекта: GUID, описание и имя. GUID (глобально-уникальный идентификатор) однозначно идентифицирует драйвер. Описание и имя представляют собой строки для неформальной идентификации драйвера. Функция DriverAvailable() сохраняет сведения о каждом драйвере в массиве с именем driver и отслеживает количество драйверов в переменной totaldrivers. Наконец, функция DriverAvailable() возвращает DDNUMRET_OK, показывая, что перечисление драйверов должно продолжаться. При получении кода возврата DDENUMRET_CANCEL DirectDraw прекращает перечисление драйверов.

    Если была установлена библиотека DirectX и в системе присутствует видеоустройство, поддерживаемое DirectDraw, то будет обнаружен по крайней мере один драйвер DirectDraw. Этот драйвер соответствует первичному видеоустройству (тому, что используется Windows). Его GUID равен нулю, строка описания содержит текст «Primary Display Driver», а строка имени — «display». При перечислении дополнительных драйверов используются нормальные значения GUID. Строки описаний и имен зависят от типов видеоустройств и версий драйверов.

    Выбор драйвера

    После того как все драйверы DirectDraw будут перечислены, функция OnCreate() выбирает один из них. Выбор драйвера по умолчанию может быть переопределен в производных классах с помощью виртуальной функции SelectDriver(). Возвращаясь к листингу 3.1, мы видим, что величина, возвращаемая функцией SelectDriver(), используется в качестве индекса массива driver (причем значения индекса начинаются с нуля). Индекс показывает, какой GUID (и, следовательно, драйвер) должен использоваться для инициализации DirectDraw. Версия SelectDriver() из класса DirectDrawWin выглядит так:

    virtual int SelectDriver() {

     return 0;

    }

    По умолчанию SelectDriver() возвращает 0, тем самым показывая, что должно использоваться первичное видеоустройство. Чтобы изменить ее поведение, следует переопределить SelectDriver() в классе, производном от DirectDrawWin. В нашем примере класс BounceWin переопределяет SelectDriver() так, чтобы в случае обнаружения нескольких драйверов выводилось меню:

    int bounceWin::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();

    }

    Эта функция сначала определяет количество обнаруженных драйверов с помощью функции GetNumDrivers(), которая просто возвращает значение закрытой переменной totaldrivers. Если в системе обнаружен всего один драйвер, выводить меню незачем, поэтому функция возвращает 0, чтобы использовался первичный драйвер.

    Если в системе доступно несколько драйверов, функция SelectDriver() создает меню, состоящее из строк с описаниями драйверов. Класс DriverDialog (простой класс диалогового окна, сгенерированный ClassWizard) выводит диалоговое окно, в котором пользователь может выбрать нужный драйвер. На рис. 3.9 изображено такое окно с двумя строками: первичным драйвером и драйвером DirectX 2 для видеокарты Orchid Righteous 3D.

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

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

    Третья задача, выполняемая функцией OnCreate(), — инициализация DirectDraw. Я снова привожу соответствующий фрагмент листинга 3.1:

    LPDIRECTDRAW ddraw1;

    DirectDrawCreate(driver[driverindex].guid, &ddraw1, 0);

    HRESULT r;

    r=ddraw1->QueryInterface(IID_IDirectDraw2, (void**)&ddraw2);

    if (r!=S_OK) {

     AfxMessageBox("DirectDraw2 interface not supported");

     return -1;

    }

    ddraw1->Release(), ddraw1=0;

    ddraw2->SetCooperativeLevel(GetSafeHwnd(), DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX);

    Сначала мы объявляем ddraw1, указатель на интерфейс DirectDraw. Это локальный и, следовательно, временный указатель. Класс DirectDrawWin объявляет ddraw2, указатель на интерфейс DirectDraw2, однако мы не сможем инициализировать его без интерфейса DirectDraw. Функция DirectDrawCreate() инициализирует указатель ddraw1. Первый аргумент является указателем на GUID выбранного драйвера. Адрес указателя ddraw1 передается в качестве второго аргумента. Последний аргумент DirectDrawCreate() должен быть равен 0.

    Рис. 3.9. Диалоговое окно для выбора драйвера


    После того как интерфейс DirectDraw будет инициализирован, им можно воспользоваться для получения указателя на интерфейс DirectDraw2. Для этого следует вызвать функцию QueryInterface() и передать ей GUID интерфейса DirectDraw2, определенный под именем IID_IDirectDraw2. Если вызов QueryInterface() заканчивается неудачно, программа выводит диалоговое окно и завершает работу. Фактически мы требуем присутствия библиотеки DirectX версии 2 и выше (потому что интерфейс DirectDraw2 впервые появился в DirectX 2). Если вызов QueryInterface() окажется успешным, указатель ddraw1 освобождается. Попеременный вызов функций интерфейсов DirectDraw и DirectDraw2 не рекомендуется, поэтому освобождение указателя на интерфейс DirectDraw гарантирует, что в оставшейся части кода будет использоваться только интерфейс DirectDraw2.

    Затем мы вызываем функцию SetCooperativeLevel() и в качестве аргументов передаем ей логический номер нашего окна и три флага. По логическому номеру организуется взаимодействие окна с DirectDraw. При вызове SetCooperativeLevel() использованы три флага: DDSCL_EXCLUSIVE, DDSCL_FULLSCREEN и DDSCL_ALLOWMODEX. Флаги монопольного и полноэкранного режима обычно используются вместе для получения максимальных полномочий по управлению видеоустройством. Последний флаг означает, что все поддерживаемые видеорежимы Mode X должны быть доступны для выбора в программе. В Windows NT этот флаг игнорируется.

    Обнаружение видеорежимов

    На следующем этапе необходимо определить все видеорежимы, поддерживаемые инициализированным драйвером DirectDraw. Для перечисления видеорежимов используется функция EnumDisplayModes(), аналогичная рассмотренной выше функции DirectDrawEnumerate(). В обоих случаях для перечисления используются косвенно вызываемые функции, а также предоставляются средства для передачи им данных, определяемых приложением. В нашем случае DisplayModeAvailable() является функцией косвенного вызова (callback function), а указатель this ссылается на произвольные данные. Функция DisplayModeAvailable() выглядит так:

    HRESULT WINAPI DirectDrawWin::DisplayModeAvailable(LPDDSURFACEDESC desc, LPVOID p) {

     DirectDrawWin* win=(DirectDrawWin*)p;

     int& count=win->totaldisplaymodes;

     if (count==MAXDISPLAYMODES) return DDENUMRET_CANCEL;

     win->displaymode[count].width = desc->dwWidth;

     win->displaymode[count].height = desc->dwHeight;

     win->displaymode[count].depth = desc->ddpfPixelFormat.dwRGBBitCount;

     count++;

     return DDENUMRET_OK;

    }

    DirectDraw вызывает функцию DisplayModeAvailable() для каждого поддерживаемого видеорежима. Структура DDSURFACEDESC, передаваемая косвенно вызываемой функции, содержит описание обнаруженного видеорежима. Функция DisplayModeAvailable() сохраняет разрешение экрана и глубину пикселей в специальном массиве, называемом displaymode. В переменной total displaymodes хранится количество обнаруженных видеорежимов; если значение totaldisplaymodes достигает MAXDISPLAYMODES, перечисление завершается возвратом кода DDENUMRET_CANCEL.

    Затем функция OnCreate() сортирует элементы displaymode так, чтобы режимы с низким разрешением находились в начале массива. Это делается с помощью функции Win32 qsort(), которой передается функция косвенного вызова для «сравнения» видеорежимов. В нашем случае используется функция CompareModes(), которая сравнивает видеорежимы сначала по разрешению, а затем по глубине пикселей. Я пропускаю дальнейшее обсуждение CompareModes(), потому что оно не имеет никакого отношения к DirectDraw.

    Выбор видеорежима

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

    virtual int SelectInitialDisplayMode() = 0;

    В C++ чисто виртуальные функции обязательно должны переопределяться, в противном случае класс не будет компилироваться. Однако со стороны DirectDrawWin было бы нечестно требовать от производного класса выбора исходного видеорежима, не предоставляя ему средств для просмотра возможных вариантов (переменные класса, в которых хранятся сведения о видеорежимах, являются закрытыми (private)). Для этой цели в классе DirectDrawWin предусмотрены функции GetNumDisplayModes() и GetDisplayModeDimensions(). В версии SelectInitialDisplayMode() класса BounceWin эти функции используются для выбора исходного режима:

    int BounceWin::SelectInitialDisplayMode() {

     int i, nummodes=GetNumDisplayModes();

     DWORD w,h,d;

     for (i=0;i<nummodes;i++) {

      GetDisplayModeDimensions(i, w, h, d);

      if (w==desiredwidth && h==desiredheight && d==desireddepth) return i;

     }

     for (i=0;i>nummodes;i++)  {

      GetDisplayModeDimensions(i, w, h, d);

      if (d==desireddepth) return i;

     }

     return 0;

    }

    Функция сначала определяет количество режимов функцией GetNumDisplayModes(), а затем в цикле пытается найти видеорежим с заданным разрешением и глубиной пикселей. Атрибуты каждого видеорежима извлекаются функцией GetDisplayModeDimensions(); если совпадение будет найдено, возвращается индекс видеорежима. В противном случае другой цикл ищет любой видеорежим с заданной глубиной пикселей. Поскольку цикл начинается с начала массива displaymode, с большей вероятностью будут выбираться режимы низкого разрешения. Если не найдено ни одного видеорежима с заданной глубиной пикселей, возвращается значение 0 — оно говорит о том, что следует использовать видеорежим с минимальным разрешением. Код возврата –1 сообщает DirectDrawWin о том, что ни один приемлемый видеорежим так и не был найден и работу приложения следует завершить.

    Активизация видеорежима

    На предпоследнем этапе происходит активизация выбранного режима. Для этого используется функция ActivateDisplayMode(), которая на самом деле выполняет и задачу последнего этапа (создание поверхностей приложения). Код этой функции приведен в листинге 3.2.


    Листинг 3.2. Функция ActivateDisplayMode()

    BOOL DirectDrawWin::ActivateDisplayMode(int mode) {

     if (mode<0 || mode>=totaldisplaymodes) return FALSE;

     DWORD width = displaymode[mode].width;

     DWORD height = displaymode[mode].height;

     DWORD depth = displaymode[mode].depth;

     displayrect.left=0;

     displayrect.top=0;

     displayrect.right=width;

     displayrect.bottom=height;

     displaydepth=depth;

     ddraw2->SetDisplayMode(width, height, depth, rate, 0);

     curdisplaymode = mode;

     TRACE("------------------- %dx%dx%d (%dhz) ---------------\n",   width, height, depth, rate);

     if (CreateFlippingSurfaces()==FALSE) {

      FatalError("CreateFlippingSurfaces() failed");

      return FALSE;

     }

     StorePixelFormatData();

     if (CreateCustomSurfaces()==FALSE) {

      FatalError("CreateCustomSurfaces() failed");

      return FALSE;

     }

     return TRUE;

    }

    Нужный видеорежим определяется параметром mode, который сначала проверяется на правильность. Затем его ширина, высота и глубина извлекаются из массива displaymode и заносятся в переменные displayrect и displaydepth. Доступ к этим переменным в производных классах осуществляется с помощью функций GetDisplayRect() и GetDisplayDepth().

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

    Перед тем как рассматривать оставшуюся часть функции, следует сделать одно важное замечание. До сих пор, если функция заканчивалась неудачей и требовалось вывести сообщение, можно было использовать функцию MFC AfxMessageBox(). Пока видеорежим не изменялся, все было нормально, но после изменения видеорежима для вывода сообщений и завершения программы применяется функция FatalError(). Эта функция класса DirectDrawWin восстанавливает видеорежим Windows, выводит окно сообщения и завершает программу.

    Создание поверхностей

    Остается лишь создать поверхности, используемые в приложении. После вызова SetDisplayMode() функция ActivateDisplayMode() вызывает еще три функции: CreateFlippingSurfaces(), StorePixelFormatData() и CreateCustomSurfaces(). Функция CreateFlippingSurfaces() создает первичную поверхность с возможностью переключения страниц. Функция StorePixelFormatData() используется для чтения и записи сведений о формате пикселей в данном видеорежиме. Эта информация может пригодиться при работе с видеорежимами High и True Color. Функция CreateCustomSurfaces() отвечает за создание и инициализацию вспомогательных поверхностей, специфических для данного приложения. Начнем с функции CreateFlippingSurfaces():

    BOOL DirectDrawWin::CreateFlippingSurfaces() {

     if (primsurf) primsurf->Release(), primsurf=0;

     DDSURFACEDESC desc;

     ZeroMemory(&desc, sizeof(desc));

     desc.dwSize = sizeof(desc);

     desc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

     desc.ddsCaps.dwCaps =  DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

     desc.dwBackBufferCount = 1;

     HRESULT r=ddraw2->CreateSurface(&desc, &primsurf, 0);

     if (r!=DD_OK) return FALSE;

     DDSCAPS surfcaps;

     surfcaps.dwCaps = DDSCAPS_BACKBUFFER;

     r=primsurf->GetAttachedSurface(&surfcaps, &backsurf);

     if (r!=DD_OK) return FALSE;

     return TRUE;

    }

    Функция CreateFlippingSurfaces() вызывается при каждой инициализации нового видеорежима, поэтому ее работа начинается с освобождения ранее созданных поверхностей функцией Release(). Затем она объявляет и инициализирует экземпляр структуры DDSURFACEDESC. Эта структура описывает тип создаваемой поверхности. В соответствии с требованиями DirectDraw необходимо установить флаги для всех инициализируемых полей. В нашем случае флаги DDSD_CAPS и DDSD_BACKBUFFERCOUNT говорят о том, что мы задаем возможности поверхности (поле dwCaps) и количество вторичных буферов (поле dwBackCount). В поле dwCaps устанавливаются три флага:

    • DDSCAPS_PRIMARYSURFACE

    • DDSCAPS_FLIP

    DDSCAPS_COMPLEX

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

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

    Флаг DDSCAPS_COMPLEX используется всегда, когда происходит присоединение поверхностей. В нашем случае первичная поверхность должна быть присоединена к поверхности вторичного буфера. Затем мы присваиваем полю dwBackBufferCount значение 1, показывая, что к создаваемой первичной поверхности должен быть присоединен один вторичный буфер.

    Новая поверхность создается вызовом функции CreateSurface() интерфейса DirectDraw. Первым аргументом является указатель на структуру desc, а вторым - указатель на переменную DirectDrawWin::primsurf. Эта переменная объявлена защищенной (protected), поэтому мы можем использовать ее для доступа к первичной поверхности в своих программах. Третий аргумент функции CreateSurface() должен быть равен 0.

    Вызов CreateSurface() создает две поверхности: первичную поверхность и вторичный буфер. Позднее указатель на вторичный буфер понадобится нам для подготовки кадров. Чтобы получить этот указатель, следует вызвать функцию GetAttachedSurface() интерфейса DirectDrawSurface и передать ей структуру DDSCAPS с описанием типа интересующей нас присоединенной поверхности. Задавая флаг DDSCAPS_BACKBUFFER, мы вызываем функцию GetAttachedSurface(), которая инициализирует переменную backsurf. Она, как и переменная primsurf, объявлена защищенной, поэтому классы, производные от DirectDrawWin, могут легко обратиться к вторичному буферу.

    После того как указатели primsurf и backsurf будут инициализированы, ActivateDisplayMode() вызывает функцию StorePixelFormatData(). Эта функция с помощью функции GetPixelFormat() интерфейса DirectDrawSurface получает информацию о формате хранения цветовых RGB-составляющих для отдельных пикселей. Формат пикселей зависит от видеокарты, а иногда даже от видеорежима, так что эти сведения оказываются полезными при прямых манипуляциях с поверхностями. Функция StorePixelFormatdata() выглядит так:

    BOOL DirectDrawWin::StorePixelFormatData() {

     DDPIXELFORMAT format;

     ZeroMemory(&format, sizeof(format));

     format.dwSize=sizeof(format);

     if (backsurf->GetPixelFormat(&format)!=DD_OK)  {

      return FALSE;

     }

     loREDbit = LowBitPos(format.dwRBitMask);

     WORD hiREDbit = HighBitPos(format.dwRBitMask);

     numREDbits=(WORD)(hiREDbit-loREDbit+1);

     loGREENbit = LowBitPos(format.dwGBitMask);

     WORD hiGREENbit = HighBitPos(format.dwGBitMask);

     numGREENbits=(WORD)(hiGREENbit-loGREENbit+1);

     loBLUEbit = LowBitPos(format.dwBBitMask);

     WORD hiBLUEbit = HighBitPos(format.dwBBitMask);

     numBLUEbits=(WORD)(hiBLUEbit-loBLUEbit+1);

     return TRUE;

    }

    Структура DDPIXELFORMAT используется в функции GetPixelFormat() для получения масок, показывающих, какие биты в каждом пикселе заняты красной, зеленой и синей цветовыми составляющими. Маски точно описывают формат пикселя, но на практике работать с ними оказывается не очень удобно. Вместо того чтобы просто сохранить полученные маски, мы на основании каждой из них инициализируем два целых числа. Первое число равно позиции младшего бита цветовой составляющей, а второе — количеству бит, необходимых для ее представления. Для поверхностей True color (24- и 32-битных) цветовые составляющие всегда представляются 8 битами, но для поверхностей High color (16-битных) это число изменяется (обычно 5, но иногда 6 для зеленой составляющей).

    Класс DirectDrawWin содержит шесть переменных для описания формата пикселей: loREDbit, numREDbits, loGREENbit, numGREENbits, loBLUEbit и numBLUEbits. Они используются некоторыми функциями DirectDrawWin, однако эти переменные объявлены как защищенные (protected), поэтому к ним можно обращаться и из классов, производных от DirectDrawWin. Эти переменные будут рассмотрены в главе 5.

    На этом инициализация приложения подходит к концу. Функция ActivateDisplayMode() вызывает еще одну функцию, CreateCustomSurfaces(), которая создает вспомогательные поверхности, но к этому моменту инициализация DirectDraw уже завершена. Функция CreateCustomSurfaces() будет рассмотрена в следующем разделе.

    Но сначала давайте подведем итоги. Приложение состоит из двух объектов, BounceWin и BounceApp. Объект BounceApp отвечает за создание объекта BounceWin, а BounceWin в свою очередь инициализирует DirectDraw. Сначала он обнаруживает все имеющиеся драйверы DirectDraw, выбирает один из них и использует его для создания экземпляра интерфейса DirectDraw2. Затем он обнаруживает видеорежимы, поддерживаемые инициализированным драйвером, выбирает один из режимов и активизирует его. Далее создается первичная поверхность с возможностью переключения страниц (и вторичным буфером) и, наконец, анализируется формат пикселей для активизированного видеорежима.

    Приложение почти готово к работе, но пока у него нет графических данных. Мы подходим к следующему этапу.

    Подготовка поверхностей

    Последняя функция, вызываемая в ActivateDisplayMode() (см. листинг 3.2), — CreateCustomSurfaces(). Эта функция является чисто виртуальной, поэтому классы, производные от DirectDrawWin, должны реализовать ее. Функция CreateCustomSurfaces() создает вспомогательные поверхности, а также инициализирует переменные и структуры данных. В классе BounceWin эта функция выглядит так:

    BOOL BounceWin::CreateCustomSurfaces() {

     CString filename;

     if (GetCurDisplayDepth()==8) filename="tri08.bmp";

     else   filename="tri24.bmp";

     if (surf1) surf1->Release(), surf1=0;

     surf1=CreateSurface(filename, TRUE);

     if (surf1==0) {

      FatalError("failed to load BMP");

      return FALSE;

     }

     return TRUE;

    }

    В приложении Bounce используется одна вспомогательная поверхность, содержимое которой определяется одним из двух BMP-файлов: 8- или 24-битным. Функция использует 8-битный файл, если текущий видеорежим является 8-битным, и 24-битный файл — в противном случае. В принципе один и тот же BMP-файл можно использовать в обоих сценариях, но это неразумно. Поскольку 8-битные файлы могут состоять лишь из 256 цветов, не стоит использовать их в режимах High и True Color. В свою очередь 24-битные изображения не пользуются палитрами и могут содержать до 16 миллионов цветов. Генерация 256-цветной палитры для такого изображения становится нетривиальной задачей. Функция CreateCustomSurfaces() определяет глубину пикселей текущего видеорежима с помощью функции DirectDrawWin::GetCurDisplayDepth(). На основании полученного результата выбирается имя BMP-файла.

    Затем мы проверяем указатель surf1. Если его значение отлично от нуля, поверхность освобождается, а указатель обнуляется. Это происходит из-за того, что функция CreateCustomSurfaces() может вызываться неоднократно. Функция ActivateDisplayMode() вызывает ее при активизации видеорежима, поэтому приложение, которое за время работы меняет несколько видеорежимов, несколько раз вызовет CreateCustomSurfaces(). Если поверхность создавалась ранее, приведенный код освобождает ее.

    Затем мы вызываем функцию CreateSurface(), чтобы создать поверхность и загрузить в нее содержимое BMP-файла. Функция CreateSurface() есть в интерфейсе DirectDrawSurface, но в данном случае используется версия из класса DirectDrawWin. Функция CreateSurface() загружает BMP-файл и создает поверхность, размеры и содержимое которой определяются изображением, хранящимся в заданном BMP-файле. CreateSurface() возвращает указатель на созданную поверхность, если все прошло нормально, и ноль — в противном случае.

    Обратите внимание на то, что функция CreateSurface() получает два аргумента. Первый из них представляет собой имя загружаемого BMP-файла. Второй аргумент показывает, нужно ли устанавливать палитру BMP-файла. Для 24-битных BMP-файлов этот аргумент игнорируется.

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

    Наше приложение Bounce является очень простым, поэтому функция CreateCustom Surfaces() делает не так уж много. Реальное приложение может создавать десятки и даже сотни поверхностей. Класс DirectDrawWin содержит несколько служебных функций, которые могут пригодиться при работе с поверхностями, поэтому мы ненадолго отвлечемся от приложения Bounce и рассмотрим эти функции:

    LPDIRECTDRAWSURFACE CreateSurface(LPCTSTR filename, BOOL  installpalette=FALSE);

    LPDIRECTDRAWSURFACE CreateSurface(DWORD w, DWORD h);

    BOOL LoadSurface(LPDIRECTDRAWSURFACE surf, LPCTSTR filename);

    BOOL ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD clr, RECT* rect=0);

    BOOL ClearSurface(LPDIRECTDRAWSURFACE surf, DWORD r, DWORD g,  DWORD b, RECT* rect=0);

    BOOL GetSurfaceDimensions(LPDIRECTDRAWSURFACE surf,  DWORD& w, DWORD& h);

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

    Вторая функция — CreateSurface() — создает поверхность заданных размеров. Эта функция полезна в тех случаях, когда вам нужна новая поверхность, содержимое которой не связано с BMP-файлом. Данная версия CreateSurface() реализована так:

    LPDIRECTDRAWSURFACE DirectDrawWin::CreateSurface(DWORD w, DWORD h) {

     DWORD bytes=w*h*(displaydepth/8);

     DDSURFACEDESC desc;

     ZeroMemory(&desc, 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 |   DDSCAPS_VIDEOMEMORY;

     LPDIRECTDRAWSURFACE surf;

     HRESULT r=ddraw2->CreateSurface(&desc, &surf, 0);

     if (r==DD_OK) {

      TRACE("CreateSurface(%d,%d) created in video memory \n", w, h);

      return surf;

     }

     desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;

     r=ddraw2->CreateSurface(&desc, &surf, 0);

     if (r==DD_OK) {

      TRACE("CreateSurface(%d,%d) allocated in system memory \n", w, h);

      return surf;

     }

     TRACE("CreateSurface(%d,%d) failed\n", w, h);

     return 0;

    }

    Функция CreateSurface() с помощью структуры DDSURFACEDESC описывает поверхность, размеры которой равны передаваемым параметрам w и h. Поле dwFlags показывает, какие поля структуры будут инициализироваться. Поля dwWidth и dwHeight определяют размеры поверхности, а поле dwCaps — ее возможности. Обратите внимание на флаг DDSCAPS_VIDEOMEMORY, согласно которому создаваемая поверхность должна находиться в видеопамяти.

    Затем мы вызываем функцию DirectDraw CreateSurface(). В качестве первого аргумента передается указатель на структуру с описанием поверхности; если вызов окажется успешным, указатель surf будет ссылаться на созданную поверхность.

    При успешном создании поверхности макрос MFC TRACE() выводит отладочное сообщение, а вы получаете указатель surf. Тем не менее раз мы явно указали, что поверхность должна находиться в видеопамяти, при нехватке последней вызов CreateSurface() может закончиться неудачно. В этом случае мы изменяем поле dwCaps, заносим в него флаг DDSCAPS_SYSTEMMEMORY и снова вызываем функцию CreateSurface(). Скорее всего, вторая попытка окажется успешной; если и на этот раз поверхность не будет создана, функция возвратит 0.

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

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

    BOOL DirectDrawWin::ClearSurface(LPDIRECTDRAWSURFACE surf,    DWORD clr, RECT* rect) {

     if (surf==0) return FALSE;

     DDBLTFX bltfx;

     ZeroMemory(&bltfx, sizeof(bltfx));

     bltfx.dwSize = sizeof(bltfx);

     bltfx.dwFillColor = clr;

     HRESULT r;

     r=surf->Blt(rect, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &bltfx);

     return r==DD_OK;

    }

    Функция ClearSurface() получает три аргумента: указатель на заполняемую поверхность; величину, присваиваемую каждому пикселю; и необязательную структуру RECT, которая определяет заполняемую область поверхности.

    После проверки указателя мы подготавливаем экземпляр структуры DDBLTFX. Полю dwFillColor присваивается величина, используемая для заполнения, а сама операция осуществляется функцией Blt() интерфейса DirectDrawSurface. Флаг DDBLT_COLORFILL сообщает Blt() о том, что вместо блиттинга выполняется цветовое заполнение.

    Получившаяся функция удобна, но ей не хватает универсальности. Дело в том, что величина, применяемая для заполнения поверхности, может иметь различный смысл. Например, для палитровых поверхностей она представляет собой индекс в палитре. Без предварительной проверки палитры невозможно предсказать, какой цвет будет использоваться для заполнения. Аналогичные проблемы возникают и для беспалитровых поверхностей, поскольку конкретное значение пикселя зависит от глубины и формата пикселей. Форматы пикселей особенно часто различаются в режимах High Color, поэтому заполнение поверхности конкретным цветом превращается в нетривиальную задачу.

    Вторая версия ClearSurface() получает в качестве аргументов RGB-составляющие и рассчитывает по ним конкретную величину, присваиваемую пикселям поверхности. В таком варианте функция становится более универсальной, но и работает медленнее; быстродействие особенно сильно снижается для палитровых поверхностей, потому что нужный цвет приходится искать в палитре. Код этой функции будет рассмотрен в главе 5.

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

    BOOL DirectDrawWin::GetSurfaceDimensions(LPDIRECTDRAWSURFACE surf, DWORD& w, DWORD& h) {

     if (surf==0) return FALSE;

     DDSURFACEDESC desc;

     ZeroMemory(&desc, sizeof(desc));

     desc.dwSize=sizeof(desc);

     desc.dwFlags=DDSD_WIDTH | DDSD_HEIGHT;

     if (surf->GetSurfaceDesc(&desc)!=DD_OK) return FALSE;

     w=desc.dwWidth;

     h=desc.dwHeight;

     return TRUE;

    }

    После проверки указателя мы подготавливаем экземпляр структуры DDSURFACEDESC. Нас интересуют ширина и высота поверхности, поэтому в поле dwFlags заносятся флаги DDSD_WIDTH и DDSD_HEIGHT.

    Затем мы вызываем функцию GetSurfaceDesc() интерфейса DirectDrawSurface и передаем ей указатель на структуру с описанием поверхности. Функция GetSurfaceDesc() сохраняет размеры поверхности в полях dwWidth и dwHeight. Они присваиваются переданным по ссылке переменным w и h типа DWORD, после чего функция завершается.

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

    Графический вывод

    Приложение Bounce прошло стадию инициализации, и теперь все готово к графическому выводу. Однако сначала мы посмотрим, как в классах DirectDrawWin и DirectDrawApp организуется обновление кадров.

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

    BOOL DirectDrawApp::OnIdle(LONG) {

     if (ddwin->PreDrawScene()) ddwin->DrawScene();

     return TRUE;

    }

    Функция OnIdle() вызывает функцию DirectDrawWin::PreDrawScene() и в зависимости от полученного результата вызывает функцию DrawScene(). Функция OnIdle() всегда возвращает TRUE, потому что при возврате FALSE MFC перестает ее вызывать. Функция PreDrawScene() реализована так:

    BOOL DirectDrawWin::PreDrawScene() {

     if (window_active && primsurf->IsLost()) {

      HRESULT r;

      r=primsurf->Restore();

      if (r!=DD_OK) TRACE("can't restore primsurf\n");

      r=backsurf->Restore();

      if (r!=DD_OK) TRACE("can't restore backsurf\n");

      RestoreSurfaces();

     }

     return window_active;

    }

    Функция PreDrawScene() выполняет сразу две задачи. Во-первых, она следит за тем, чтобы для неактивного приложения не выполнялись попытки обновить изображение на экране. Во-вторых, она восстанавливает поверхности приложения в случае их потери.

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

    Так как функция OnIdle() вызывает DrawScene() лишь после проверки результата PreDrawScene(), DrawScene() будет вызвана лишь в том случае, если приложение активно, а первичная и вторичная поверхности не были потеряны.

    Функция DrawScene()

    Классы, производные от DirectDrawWin, реализуют функцию DrawScene(), в которой происходит обновление экрана. Версия DrawScene() из класса BounceWin выглядит так:

    void BounceWin::DrawScene() {

     CRect limitrect=GetDisplayRect();

     x+=xinc;

     y+=yinc;

     if (x<-160 || x>limitrect.right-160) {

      xinc=-xinc;

      x+=xinc;

     }

     if (y<-100 || y>limitrect.bottom-120) {

      yinc=-yinc;

      y+=yinc;

     }

     ClearSurface(backsurf, 0);

     BltSurface(backsurf, surf1, x, y);

     primsurf->Flip(0, DDFLIP_WAIT);

    }

    Сначала функция GetDisplayRect() получает объект CRect, хранящий ширину и высоту текущего видеорежима. Эти размеры будут использоваться для ограничения перемещений растрового изображения в соответствии с видеорежимом. Далее вычисляются значения переменных x и y класса BounceWin, определяющих местонахождение растра на экране.

    Затем мы вызываем функцию ClearSurface() и передаем ей два аргумента: указатель backsurf и 0. Это приводит к тому, что вторичный буфер заполняется черным цветом. Хотя я упоминал о том, что использование ClearSurface() иногда осложняется различными форматами пикселей, заполнение поверхностей черным работает надежно. Для палитровых поверхностей 0 означает черный цвет, потому что по умолчанию он стоит в палитре на первом месте; для беспалитровых поверхностей 0 всегда соответствует черному цвету.

    Функция DrawScene() использует функцию DirectDrawWin::BltSurface() для копирования поверхности surf1 на поверхность backsurf. Два последних аргумента BltSurface() определяют точку поверхности-приемника, куда должно быть скопировано содержимое источника. Для выполнения этой операции можно было бы воспользоваться функцией Blt() или BltFast() интерфейса DirectDrawSurface, но мы не делаем этого из-за возможного отсечения. Обратите внимание - код, определяющий положение растра, позволяет источнику выйти за пределы приемника, в результате чего может потребоваться отсечение. Мы не можем воспользоваться функцией Blt(), потому что тогда потребовалось бы присоединить к приемнику объект DirectDrawClipper, чего мы не делаем. Функция BltFast() тоже не подходит, потому что она вообще не поддерживает отсечения. Функция BltSurface() автоматически выполняет отсечение, а функции Blt() и BltFast() вызываются внутри нее.

    Но перед тем, как переходить к функции BltSurface(), мы закончим рассмотрение функции DrawScene(). Она завершается вызовом функции Flip(). При этом происходит переключение страниц, и подготовленный нами кадр отображается на экране. Функция Flip() получает два аргумента: указатель на поверхность и переменную типа DWORD, предназначенную для установки флагов. Указатель на поверхность необходим лишь в нестандартных ситуациях, когда в переключении поверхностей участвует несколько вторичных буферов. Второй аргумент обычно содержит флаг DDFLIP_WAIT, показывающий, что возврат из функции должен происходить только после того, как переключение страниц завершится.

    Функция BltSurface()

    Функция BltSurface() класса DirectDrawWin оказывается более гибкой и удобной по сравнению с функциями DirectDrawSurface::Blt() и BltFast(). Мы уже видели, как BltSurface() используется внутри функции BounceWin::DrawScene(), а сейчас рассмотрим саму функцию.

    Функция BltSurface() требует передачи четырех аргументов, а пятый аргумент необязателен. Первые два аргумента представляют собой указатели на поверхности — источник и приемник. Следующие два аргумента — координаты x и y, определяющие положение копируемой области на приемнике. По умолчанию блиттинг выполняется без цветовых ключей, однако их можно активизировать с помощью необязательного пятого параметра. Код функции BltSurface() приведен в листинге 3.3.


    Листинг 3.3. Функция BltSurface()

    BOOL DirectDrawWin::BltSurface(LPDIRECTDRAWSURFACE destsurf, LPDIRECTDRAWSURFACE srcsurf, int x, int y, BOOL srccolorkey) {

     if (destsurf==0 || srcsurf==0) return FALSE;

     BOOL use_fastblt=TRUE;

     DDSURFACEDESC destsurfdesc;

     ZeroMemory(&destsurfdesc, sizeof(destsurfdesc));

     destsurfdesc.dwSize = sizeof(destsurfdesc);

     destsurf->GetSurfaceDesc(&destsurfdesc);

     CRect destrect;

     destrect.left=0;

     destrect.top=0;

     destrect.right=destsurfdesc.dwWidth;

     destrect.bottom=destsurfdesc.dwHeight;

     DDSURFACEDESC srcsurfdesc;

     ZeroMemory(&srcsurfdesc, sizeof(srcsurfdesc));

     srcsurfdesc.dwSize = sizeof(srcsurfdesc);

     srcsurf->GetSurfaceDesc(&srcsurfdesc);

     CRect srcrect;

     srcrect.left=0;

     srcrect.top=0;

     srcrect.right=srcsurfdesc.dwWidth;

     srcrect.bottom=srcsurfdesc.dwHeight;

     // Проверить, нужно ли что-нибудь делать...

     if (x+srcrect.left>=destrect.right) return FALSE;

     if (y+srcrect.top>=destrect.bottom) return FALSE;

     if (x+srcrect.right<=destrect.left) return FALSE;

     if (y+srcrect.bottom<=destrect.top) return FALSE;

     // При необходимости выполнить отсечение

     // для прямоугольной области источника

     if (x+srcrect.right>destrect.right) srcrect.right-=x+srcrect.right-destrect.right;

     if (y+srcrect.bottom>destrect.bottom) srcrect.bottom-=y+srcrect.bottom-destrect.bottom;

     CRect dr;

     if (x<0) {

      srcrect.left=-x;

      x=0;

      dr.left=x;

      dr.top=y;

      dr.right=x+srcrect.Width();

      dr.bottom=y+srcrect.Height();

      use_fastblt=FALSE;

     }

     if (y<0) {

      srcrect.top=-y;

      y=0;

      dr.left=x;

      dr.top=y;

      dr.right=x+srcrect.Width();

      dr.bottom=y+srcrect.Height();

      use_fastblt=FALSE;

     }

     DWORD flags;

     if (use_fastblt) {

      flags=DDBLTFAST_WAIT;

      if (srccolorkey) flags |= DDBLTFAST_SRCCOLORKEY;

      destsurf->BltFast(x, y, srcsurf, &srcrect, flags);

     } else {

      flags=DDBLT_WAIT;

      if (srccolorkey) flags |= DDBLT_KEYSRC;

      destsurf->Blt(&dr, srcsurf, &srcrect, flags, 0);

     }

     return TRUE;

    }

    Сначала функция BltSurface() проверяет указатели на поверхности. Если хотя бы один из них равен нулю, функция возвращает FALSE, тем самым сообщая о неудаче. Если проверка прошла успешно, два объекта CRect инициализируются в соответствии с размерами поверхностей, полученными с помощью функции DirectDrawSurface::GetSurfaceDesc().

    Затем BltSurface() проверяет, что попадает ли точка назначения в границы приемника. Если координаты x и y таковы, что копия не пересекается с поверхностью приемника, блиттинг не нужен, поэтому мы просто выходим из функции.

    Если же с точкой назначения все в порядке, функция проверяет, нужно ли выполнять отсечение. Если отсечение не требуется, блит-операция для достижения максимального быстродействия выполняется функцией BltFast(). Если отсечение все же необходимо, возможно, придется пользоваться функцией Blt().

    Если отсечение выполняется по правому или нижнему краю источника, функция BltFast() справится с задачей и обрежет выступающую часть копируемой области. Если же отсечение происходит по верхнему или левому краю, приходится работать с функцией Blt(), потому что BltFast() не позволяет задать прямоугольную область приемника. После выполнения блиттинга BltSurface() возвращает TRUE как признак успешного завершения.

    Восстановление поверхностей

    Наше приложение благополучно инициализируется и выводит графические данные. Теперь необходимо справиться с возможной потерей поверхностей. При рассмотрении функции DirectDrawWin::PreDrawScene мы видели, что DirectDrawWin вызывает виртуальную функцию RestoreSurfaces(), чтобы производный класс получил возможность восстановить потерянные поверхности. Функция RestoreSurfaces() отвечает за восстановление как потерянной памяти поверхности, так и ее содержимого. Функция BounceWin::RestoreSurfaces() выглядит так:

    void BounceWin::RestoreSurfaces() {

     if (surf1->IsLost()==FALSE) return;

     CString filename;

     if (GetCurDisplayDepth()==8) filename="tri08.bmp";

     else filename="tri24.bmp";

     surf1->Restore();

     LoadSurface(surf1, filename);

    }

    DirectDraw может отнимать у неактивного приложения только поверхности, находящиеся в видеопамяти, так что нет смысла в восстановлении поверхностей из системной памяти. Поэтому RestoreSurfaces() сначала проверяет, была ли потеряна единственная вспомогательная поверхность нашего приложения, и если нет — функция прекращает работу. Если же поверхность была потеряна, мы восстанавливаем ее память функцией Restore(), а содержимое — функцией LoadSurface().

    Завершение

    Как бы ни была хороша программа Bounce, рано или поздно вам захочется убрать ее с экрана. Нажатие клавиши Escape завершает работу программы. Это происходит в обработчике OnKeyDown():

    void bounceWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {

     if (nChar==VK_ESCAPE) PostMessage(WM_CLOSE);

     DirectDrawWin::OnKeyDown(nChar, nRepCnt, nFlags);

    }

    Приложение завершает работу, отправляя сообщение WM_CLOSE. В нашем приложении на это сообщение реагирует и класс окна, и класс приложения. Класс окна отвечает сообщением WM_DESTROY, для которого в DirectDrawWin предусмотрен обработчик OnDestroy(). Класс DirectDrawWin в данном обработчике освобождает объекты DirectDraw и всю память, занимаемую приложением. Функция OnDestroy() выглядит так:

    void DirectDrawWin::OnDestroy() {

     if (primsurf) primsurf->Release(), primsurf=0;

      if (palette) palette->Release(), palette=0;

      if (ddraw2) ddraw2->Release(), ddraw2=0;

      for (int i=0;i<totaldrivers;i++) {

       if (driver[i].guid) delete[] driver[i].guid;

       free(driver[i].desc);

       free(driver[i].name);

      }

     }

    }

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

    Класс приложения обрабатывает завершение в функции ExitInstance(), в которой удаляется класс окна:

    int DirectDrawApp::ExitInstance() {

     delete ddwin;

     return CWinApp::ExitInstance();

    }

    На этом наше знакомство с программой Bounce заканчивается. Однако до сих пор речь шла только о полноэкранных приложениях. Оставшаяся часть этой главы посвящена оконным приложениям.

    Оконные приложения

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

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

    В начале этой главы мы воспользовались DirectDraw AppWizard и создали приложение Bounce. При этом мы указали, что создаваемая программа должна быть полноэкранной. Чтобы получить рассматриваемый ниже код, следует снова запустить AppWizard и выбрать оконное приложение.

    Структура приложения

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

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

    В полноэкранном варианте класса DirectDrawWin функция OnCreate() инициализирует DirectDraw за несколько этапов. Оконный вариант выглядит проще, потому что ему не приходится перечислять драйверы DirectDraw или видеорежимы. Оконная версия функции OnCreate() выглядит так:

    int DirectDrawWin::OnCreate(LPCREATESTRUCT) {

     LPDIRECTDRAW ddraw1;

     DirectDrawCreate(0, &ddraw1, 0);

     ddraw1->QueryInterface(IID_IDirectDraw2, (void**)&ddraw2);  ddraw1->Release(), ddraw1=0;  ddraw2->SetCooperativeLevel(GetSafeHwnd(), DDSCL_NORMAL);

     DetectDisplayMode();

     if (CreateFlippingSurfaces()==FALSE) {

      AfxMessageBox("CreateFlippingSurfaces() failed");

      return FALSE;

     }

     if (CreateCustomSurfaces()==FALSE) {

      AfxMessageBox("CreateCustomSurfaces() failed");

      return FALSE;

     }

     return 0;

    }

    Сначала указатель на интерфейс DirectDraw(ddraw1) инициализируется функцией DirectDrawCreate(). Указатель ddraw1, как и в полноэкранной версии, используется только для получения указателя на интерфейс DirectDraw2, после чего освобождается.

    Затем функция OnCreate() вызывает функцию SetCooperativeLevel(). В полноэкранном приложении уровень кооперации определялся тремя флагами: DDSCL_EXCLUSIVE, DDSCL_FULLSCREEN и DDSCL_ALLOWMODEX. В данном случае используется только флаг DDSCL_NORMAL.

    Функция DetectDisplayMode() инициализирует некоторые переменные класса DirectDrawWin. Она выглядит так:

    BOOL DirectDrawWin::DetectDisplayMode() {

     DDSURFACEDESC desc;

     ZeroMemory(&desc, sizeof(desc));

     desc.dwSize=sizeof(desc);

     if (ddraw2->GetDisplayMode(&desc)!=DD_OK) {

      TRACE("GetDisplayMode() failed\n");

      return FALSE;

     }

     displayrect.left=0;

     displayrect.top=0;

     displayrect.right=desc.dwWidth;

     displayrect.bottom=desc.dwHeight;

     displaydepth=desc.ddpfPixelFormat.dwRGBBitCount;

     return TRUE;

    }

    Функция DetectDisplayMode() с помощью функции GetDisplayMode() интерфейса DirectDraw получает информацию о текущем видеорежиме Windows. Говоря точнее, разрешение экрана и глубина пикселей текущего видеорежима сохраняются в переменных displayrect и displaydepth.

    Далее OnCreate() вызывает функцию CreateFlippingSurfaces(). Хотя оконное приложение не может выполнять настоящего переключения страниц (как можно было бы решить, исходя из имени функции), имя было сохранено, потому что создаваемые в ней поверхности эмулируют переключение страниц. Код функции приведен в листинге 3.4.


    Листинг 3.4. Функция CreateFlippingSurfaces() в оконном приложении

    BOOL DirectDrawWin::CreateFlippingSurfaces() {

     HRESULT r;

     DDSURFACEDESC desc;

     desc.dwSize = sizeof(desc);

     desc.dwFlags = DDSD_CAPS;

     desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

     r=ddraw2->CreateSurface(&desc, &primsurf, 0);

     if (r!=DD_OK) {

      TRACE("FAILED to create 'primsurf'\n");

      return FALSE;

     }

     r=ddraw2->CreateClipper(0, &clipper, 0);

     if (r!=DD_OK) {

      TRACE("CreateClipper() failed\n");

      return FALSE;

     }

     r=clipper->SetHWnd(0, GetSafeHwnd());

     if (r!=DD_OK) {

      TRACE("SetHWnd() failed\n");

      return FALSE;

     }

     r=primsurf->SetClipper(clipper);

     if (r!=DD_OK) {

      TRACE("SetClipper() failed\n");

      return FALSE;

     }

     ZeroMemory(&desc, sizeof(desc));

     desc.dwSize = sizeof(desc);

     desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;

     desc.dwWidth = displayrect.Width();

     desc.dwHeight = displayrect.Height();

     desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;

     r=ddraw2->CreateSurface(&desc, &backsurf, 0);

     if (r!=DD_OK) {

      TRACE("failed to create 'backsurf' in video\n");

      videobacksurf=FALSE;

     } else {

      TRACE("Created backsurf in video\n");

      videobacksurf=TRUE;

     }

     return TRUE;

    }

    Сначала мы создаем первичную поверхность. В полноэкранном варианте код выглядит по-другому, потому что здесь создается обычная, несоставная первичная поверхность. В структуре DDSURFACEDESC мы описываем первичную поверхность, используя только флаг DDSCAPS_PRIMARYSURFACE. Затем описанная поверхность создается функцией CreateSurface() интерфейса DirectDraw.

    Далее функция CreateClipper() интерфейса DirectDraw создает объект отсечения. CreateClipper() получает три аргумента, однако первый и последний из них чаще всего равны нулю. Второй аргумент представляет собой адрес указателя на интерфейс DirectDrawClipper. В нашем случае используется переменная класса DirectDrawWin с именем clipper.

    Объект отсечения нужен для ограничения вывода в программе. Поскольку наше приложение работает в окне, которое находится на рабочем столе вместе с другими окнами, при обновлении изображения необходимо учитывать присутствие этих окон. Чтобы объект отсечения автоматически выполнял свою работу, его необходимо присоединить к окну функцией SetHWnd() интерфейса DirectDrawClipper. Функция SetHWnd() получает два аргумента — двойное слово (DWORD), которое зарезервировано для будущего использования и пока должно быть равно нулю, и логический номер окна приложения.

    Далее объект отсечения присоединяется к первичной поверхности приложения функцией SetClipper() интерфейса DirectDrawSurface. После такого присоединения можно осуществлять блиттинг на первичную поверхность с помощью функции Blt() интерфейса DirectDrawSurface. Использовать функцию BltFast() нельзя, потому что она не поддерживает отсечения.

    Последнее, что происходит в функции CreateFlippingSurface(),  - создание поверхности вторичного буфера. В идеальном варианте нам удастся найти свободную видеопамять в объеме, достаточном для создания внеэкранной поверхности, которая по ширине и высоте совпадает с первичной поверхностью. Я называю такой вариант идеальным из-за преимущества по скорости, характерного для блит-операций в пределах видеопамяти. Кроме того, поскольку вторичный буфер по размерам совпадает с первичной поверхностью, он подойдет для окна любого размера.

    Функция CreateFlippingSurfaces() пытается создать «идеальный» вторичный буфер, для чего используются флаг DDSCAPS_VIDEOMEMORY и функция CreateSurface(). Если вызов заканчивается успешно, флаг videobacksurf получает значение TRUE, а функция завершает работу. В противном случае вторичный буфер не создается, а флагу videobacksurf присваивается значение FALSE.

    В том варианте вторичный буфер создается приложением в системной памяти позднее, в обработчике OnSize(). Функция OnSize() вызывается при изменении размеров окна приложения. Создавая вторичный буфер по размерам клиентской области окна, мы экономим память. Функция OnSize() выглядит так:

    void DirectDrawWin::OnSize(UINT nType, int cx, int cy) {

     CWnd::OnSize(nType, cx, cy);

     CFrameWnd::GetClientRect(&clientrect);

     CFrameWnd::ClientToScreen(&clientrect);

     if (videobacksurf) return;

     DDSURFACEDESC desc;

     ZeroMemory(&desc, sizeof(desc));

     desc.dwSize = sizeof(desc);

     desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;

     desc.dwWidth = clientrect.Width();

     desc.dwHeight = clientrect.Height();

     desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;

     if (backsurf) backsurf->Release(), backsurf=0;

     HRESULT r=ddraw2->CreateSurface(&desc, &backsurf, 0);

     if (r!=DD_OK)  {

      TRACE("failed to create 'backsurf'\n");

      return;

     } else TRACE("backsurf w=%d h=%d\n", clientrect.Width(), clientrect.Height());

    }

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

    Графический вывод

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

    void BounceWin::DrawScene() {

     ClearSurface(backsurf, 0);

     CRect client=GetClientRect();

     int width=client.Width();

     int height=client.Height();

     x+=xinc;

     y+=yinc;

     if (x<-160 || x>width-160) {

      xinc=-xinc;

      x+=xinc;

     }

     if (y<-100 || y>height-100) {

      yinc=-yinc;

      y+=yinc;

     }

     BltSurface(backsurf, surf1, x, y);

     int offsetx=client.left;

     int offsety=client.top;

     RECT srect;

     srect.left=0;

     srect.top=0;

     srect.right=client.Width();

     srect.bottom=client.Height();

     RECT drect;

     drect.left=offsetx;

     drect.top=offsety;

     drect.right=offsetx+client.Width();

     drect.bottom=offsety+client.Height();

     primsurf->Blt(&drect, backsurf, &srect, DDBLT_WAIT, 0);

    }

    Функция DrawScene() выполняет две блит-операции. Первая копирует содержимое поверхности surf1 на внеэкранную поверхность, которая используется в качестве вторичного буфера. Обратите внимание на применение функции BltSurface(), рассмотренной нами выше. Автоматическое отсечение, выполняемое BltSurface(), позволяет произвольно выбирать позицию на поверхности surf1.

    Вторая блит-операция копирует содержимое вторичного буфера на первичную поверхность. На этот раз используется функция Blt(), поскольку к первичной поверхности присоединен объект отсечения. Структуры srect и drect типа RECT определяют области источника и приемника, участвующие в блиттинге. Заметьте, что при вычислении области приемника используются переменные offsetx и offsety, в которых хранятся координаты клиентской области окна. Если убрать эти смещения из структуры drect, программа всегда будет выводить изображение в левом верхнем углу экрана независимо от расположения окна.

    Заключение

    В этой главе мы изучили почти весь код, сгенерированный AppWizard. Рассмотренное нами базовое приложение нетрудно изменить, поэтому попробуйте немного поэкспериментировать. Например, попытайтесь добавить в программу Bounce дополнительные поверхности или замените вызовы BltSurface() на BltFast() и посмотрите, что получится.

    В оставшейся части книги речь в основном пойдет о том, какие изменения следует внести в базовый код, чтобы добиться конкретного результата. В главе 4 мы напишем программу, которая в полной мере использует возможности DirectDraw по переключению видеорежимов.







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх