netlib.narod.ru< Назад | Оглавление | Далее >

Окна, порты просмотра и устройства

Термин окно обладает множеством значений. В традиционном графическом программировании он соответствует способу проектирования изображения на плоскую поверхность, а у многих из нас он вызовет ассоциации с объектами в знакомой операционной системе с громадным количеством разных API. Как бы лично вы ни понимали этот термин (я не собираюсь никого обижать), он неимоверно перегружен значениями и уступает в этом смысле разве что термину объект. Как бы тщательно я ни подбирал слова при описании механизма визуализации, кто-нибудь все равно не поймет меня или будет настаивать на том, что термин используется неверно. Лично я считаю, что любая общепринятая терминология, будь она трижды неверна, вполне подходит и для меня. То же самое относится и к документации. Если в ней что-то именуется «окном», то я не буду называть это «что-то» иначе. Следовательно, даже если вам не нравится мой подход к описанию механизма визуализации, моей вины в этом нет — термины выбирали другие.

Давайте начнем с самого начала. Окно — объект Microsoft Windows, который обрабатывает сообщения и отображается в приложениях. Портом просмотра (viewport) называется математическое описание того, как набор объектов в трехмерном пространстве отображается в окне. Устройством (device) называется программа, связанная с реальным устройством, отвечающим за работу видеосистемы на вашем компьютере. Чтобы создать трехмерный макет в приложении, необходимо иметь окно, порт просмотра и устройство. На самом деле с одним устройством может быть связано несколько портов просмотра и несколько окон, однако мы построим систему с одним окном, одним портом просмотра и одним устройством. Вы управляете работой окна; управление портом просмотра и устройством осуществляет механизм визуализации.

GDI и DirectDraw

Давайте посмотрим, что же на самом деле происходит, когда мы открываем окно на рабочем столе Windows. На рис. 2.2 изображено окно, помещенное в произвольном месте рабочего стола.


Рис. 2.2. Окно на рабочем столе

Рис. 2.2. Окно на рабочем столе


Теперь спустимся на аппаратный уровень и посмотрим на карту памяти видеоадаптера (рис. 2.3).


Рис. 2.3. Карта памяти видеоадаптера

Рис. 2.3. Карта памяти видеоадаптера


Допустим, у вас установлен видеоадаптер с 2 Мб видеопамяти, как показано на рис. 2.3. Вы работаете в разрешении 1024 х 768 с 256 цветами, так что ваша видеокарта фактически использует только 786 432 байта (1024 х 768) видеопамяти. Эта часть изображена на рис. 2.3 как активная видеопамять. Открытое окно имеет размеры приблизительно 512 х 400 пикселей, для хранения которых необходимо 204 800 байт видеопамяти.

Теперь предположим, что вы хотите воспроизвести в своем открытом окне трехмерную анимацию. Для этого вы задаете размеры окна и его положение на рабочем столе. Тем самым определяется область видеопамяти, используемая для отображения содержимого окна («область памяти окна» на рис. 2.3). Если бы для рисования в вашем окне применялись стандартные функции Windows GDI, то при получении запроса на вывод модуль GDI обратился бы к драйверу видеоустройства для установки тех пикселей в видеопамяти, которые бы приводили к нужному эффекту. На рис. 2.4 изображена программная иерархия, с которой обычно приходится иметь дело в Windows-программах.


Рис. 2.4. Графический вывод в Windows

Рис. 2.4. Графический вывод в Windows


Например, для того чтобы нарисовать в окне прямоугольник, следует вызвать функцию Rectangle. GDI спрашивает драйвер видеоустройства, умеет ли тот рисовать прямоугольники; если не умеет, GDI «договаривается» с драйвером о каком-нибудь другом способе рисования прямоугольника (построение множества линий или чего-нибудь в этом роде). Затем драйвер устройства обращается к содержимому видеопамяти или, если нам повезло, пользуется аппаратными особенностями видеокарты для непосредственного рисования прямоугольника. Как вы думаете, быстро проходит этот процесс или медленно? Правильный ответ — не очень медленно, но и быстрым его никак не назовешь. Все дело в универсальности GDI, за которую приходится расплачиваться.

Разве не замечательно было бы обойти GDI и драйвер видеоустройства и напрямую работать с видеопамятью? Конечно, это будет гораздо быстрее, но тогда вам придется досконально изучить работу всех видеокарт на планете. Библиотека DirectDraw предлагает идеальный вариант — вы обращаетесь к драйверу видеоустройства с запросом на прямой доступ к видеопамяти, и если драйвер разрешит, вы сможете непосредственно изменять значения пикселей на экране. Если же драйвер не сможет предоставить прямого доступа к видеопамяти, он по крайней мере создаст иллюзию того, что вы работаете с ней, хотя часть работы при этом будет выполняться самим драйвером. Приложение может пользоваться функциями GDI или функциями DirectDraw в зависимости от своих требований к производительности. При установке DirectDraw процесс графического вывода происходит в соответствии с рис. 2.5.


Рис. 2.5. Графический вывод с использованием DirectDraw

Рис. 2.5. Графический вывод с использованием DirectDraw


DirectDraw также позволяет выполнять операции пересылки битовых блоков (бит-блит, bitblt), по возможности реализуемые с помощью аппаратных средств видеокарты. Вызывая блитовые функции DirectDraw, вы получаете либо потрясающую производительность (при прямом использовании особенностей адаптера), либо просто великолепную (при прямом переносе данных в видеопамять посредством кода DirectDraw).

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

Пересылка видеоданных

Чтобы понять, почему данные в пределах видеопамяти копируются значительно быстрее, чем из основной памяти, необходимо понимать, как работает аппаратная часть компьютера. На рис. 2.6 изображена упрощенная модель работы основных компонентов видеосистемы компьютера.


Рис. 2.6. Примерная архитектура видеосистемы

Рис. 2.6. Примерная архитектура видеосистемы


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

На самом деле пересылку данных в видеопамяти можно ускорить и дальше. Если ваше приложение будет работать в полноэкранном монопольном режиме (full-screen exclusive mode), видеокарта сможет переключаться между двумя страницами видеопамяти, благодаря чему анимация достигает производительности, присущей разве что играм для DOS.

Так какое же отношение все сказанное имеет к механизму визуализации? Он должен очень быстро производить графический вывод (прямо в видеопамять или, еще предпочтительнее, с использованием аппаратных средств видеокарты для поддержки работы с трехмерной графикой). К тому времени, когда вы будете читать эту книгу, на рынке уже появятся видеокарты с аппаратным ускорением трехмерной графики, стоимость которых не будет превышать $200. Как же происходит пересылка данных в этих условиях? Механизм визуализации обращается к функциям промежуточного программного уровня (в данном случае — непосредственного режима Direct3D), который сообщает видеокарте о необходимости выполнить ряд примитивных операций по обсчету трехмерной графики. Если видеокарта не может справиться с подобной задачей, необходимые функции эмулируются с привлечением программных драйверов Direct3D. При наличии таких программных компонентов мы получаем новую модель (рис. 2.7), при которой любое приложение (не только механизм визуализации) сможет вызвать набор трехмерных функций, которые будут реализованы с максимальной производительностью.


Рис. 2.7. Работа с трехмерной графикой с участием промежуточного уровня Direct3D

Рис. 2.7. Работа с трехмерной графикой с участием промежуточного уровня Direct3D


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

Создание устройства и порта просмотра в приложении Stage

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

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

Разумеется, создать устройство непосредственно по дескриптору окна гораздо проще, чем возиться с поверхностями DirectDraw, — и все же я пошел вторым путем. В начале работы над библиотекой 3dPlus я действительно создавал устройство по дескриптору окна. Потом на выходных я разошелся и решил «поиграть» с функциями DirectDraw. В результате у меня появился набор классов-оболочек для функций DirectDraw, так что создать устройство на основе поверхностей DirectDraw стало ничуть не сложнее, чем по дескриптору окна. Вероятно, мои слова вас не убедили, поэтому я приведу фрагмент программы, в котором создается объект-сцена в трехмерном окне нашего проекта Stage:

BOOL C3dWnd::CreateStage() 
{
    // Инициализировать объект DirectDraw
    if (!m_pDD) { 
        m_pDD = new CDirectDraw; 
    }
    if (!m_pDD->Create()) return FALSE; 

    // Установить экранный режим для окна
    if ( !m_pDD->SetWindowedMode (GetSafeHwnd(),
                                     m_iWidth,
                                     m_iHeight)) {
        return FALSE; 
    }

    // Создать объект Direct3D
    if (!m_pD3D) {
        m_pD3D = new CDirect3D; 
    }
    if (!m_pD3D->Create(m_pDD)) return FALSE; 

    // Задать цветовую модель
    if (!m_pD3D->SetMode(m_ColorModel)) return FALSE;

    // Создать сцену
    if (!m_pStage) { 
        m_pStage = new C3dStage; 
    }
    if (!m_pStage->Create(m_pD3D)) return FALSE; 

    // Присоединить текущий макет
    m_pStage->SetScene(m_pScene); 

    return TRUE; 
} 

Первая половина функции C3dWnd::CreateStage посвящена созданию объектов DirectDraw и Direct3D, предоставляющих основу для рисования трехмерных объектов в окне. Затем мы выбираем оконный режим для объекта DirectDraw (в отличие от полноэкранного режима) и задаем монохромную цветовую модель MONO для объекта Direct3D (цветовые модели рассматриваются в главе 10). Несколько последних строк создают объект C3dStage по объекту DirectDraw и присоединяют текущий макет к сцене. В свою очередь, объект-сцена C3dStage содержит объекты C3dDevice (устройство) и C3dViewport (порт просмотра), которые отвечают за взаимодействие с компонентами DirectDraw и Direct3D. Кроме того, сцена содержит объект C3dCamera; мы рассмотрим его ниже. Функция, в которой происходит фактическое создание сцены по объекту Direct3D, выглядит следующим образом:

BOOL C3dStage::Create(CDirect3D* pD3D) 
{ 
    // Удалить существующий макет
    SetScene(NULL); 

    // Создать новое устройство по поверхностям Direct3D
    if (!m_Device.Create(pD3D)) return FALSE; 

    // Задать качество
    m_Device.SetQuality(m_Quality); 

    // Создать порт просмотра
    if (!m_Viewport.Create(&m_Device, 
                           &m Camera, 
                           0, 0, 
                           m_Device.GetWidth(), 
                           m_Device.GetHeight())) {
        return FALSE; 
    } 

    return TRUE;
}

Как видите, приведенная выше функция сводится к построению объектов C3dDevice и C3dViewport. Чтобы создать устройство, мы вызываем соответствующую функцию Direct3D и передаем ей указатель на используемые компоненты DirectDraw:

BOOL C3dDevice::Create(CDirect3D* pD3D) 
{ 
    if (m_pIDevice) { 
        m_pIDevice->Release();
        m_pIDevice = NULL;
    }
    m_hr = the3dEngine.GetInterface()->CreateDeviceFromD3D( 
                       pD3D->GetD3DEngine(),
                       pD3D->GetD3DDevice(),
                       &m_pIDevice); 
    if (FAILED(m_hr)) {
        return FALSE; 
    }
    ASSERT(m_pIDevice); 

    return TRUE; 
}

Если бы устройство создавалось по дескриптору окна, а не по набору поверхностей DirectDraw, то вместо функции CreateDeviceFromD3D была бы вызвана функция CreateDeviceFromHWND.

Раз уж у меня имелась рабочая программа, в которой использовались поверхности, а не дескриптор, я решил оставить все без изменений, в качестве примера. Если когда-нибудь вам захочется самостоятельно поэкспериментировать с механизмом визуализации вместо того, чтобы пользоваться 3dPlus, можно подумать о создании устройства по дескриптору окна. Разумеется, при работе с классами библиотеки 3dPlus можно совершенно не интересоваться их внутренней реализацией и считать их чем-то вроде «черных ящиков». Тем не менее знакомство с основами работы библиотеки 3dPlus поможет при дальнейшем расширении ее функций.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz