Введение в XNA

Введение в XNA Framework

Разбить на страницы
Показывать лекцию целиком

Примеры к данному курсу Вы можете скачать здесь.

Первые версии Windows позволяли программисту работать с видеоподсистемой компьютера лишь посредством стандартного интерфейса GDI, предоставляющему программисту унифицированный доступ к различным устройствам вывода графической информации будь то видеокарта, принтер или плоттер. Интерфейс GDI очень прост в использовании - приложение работает с некоторым виртуальным устройством, а GDI самостоятельно транслирует все вызовы приложения в команды конкретной видеокарты или принтера. Преимущества данного подхода очевидны. Например, добавив в приложение всего несколько строк кода, вы можете с легкостью перенаправить вывод с экрана монитора на принтер и получить нужный результат. Кроме того, работа с виртуальным устройством не позволяет некорректно написанному приложению нанести какой-либо ощутимый ущерб стабильности операционной системе и повредить данные других приложений.

Как всегда, недостатки интерфейса GDI являются продолжением его достоинств. Любому человеку, даже поверхностно знакомому с устройством персонального компьютера, ясно, что, к примеру, видеокарта NVIDIA GeForce 5900 Ultra и лазерный принтер Canon Laser Shot LBP-1120 являются абсолютно разными устройствами. Соответственно, при проектировании универсального интерфейса для работы с данными устройствами неминуемо придeтся жертвовать эффективностью. Это не особо критично для офисных приложений, работающих с достаточно простыми изображениями и не требовательных к скорости обновления экрана. Однако для целого класса приложений (игры, системы виртуальной реальности, пакеты 3D моделирования), критичных к производительности видеоподсистемы компьютера, накладные расходы GDI оказались неприемлемыми. В результате разработчики этих приложений не стремились переносить свои разработки с DOS на Windows, что отнюдь не способствовало росту популярности операционной системы Windows.

OpenGL

Так как операционная система без развлекательных приложений вряд ли смогла бы добиться широкой популярности среди домашних пользователей, Microsoft включала сначала в Windows NT 3.5, а затем и в Windows 95 OSR2 поддержку OpenGL - известного кроссплатформенного API для разработки трехмерных графических приложений реального времени. В те времена OpenGL справедливо считался флагманом индустрии трехмерной графики реального времени. Тем не менее, у него все же был ряд недостатков:

  • Так как OpenGL предназначен исключительно для работы с графикой, он решил лишь проблемы низкой производительности графической подсистемы Windows. Работа с аудиоподсистемой компьютера по-прежнему осуществлялась с использованием стандартного медленного интерфейса MCIMedia Control Interface – интерфейс управления мультимедийными устройствами. Содержит набор стандартных команд, позволяющих осуществлять воспроизведение и запись файлов мультимедийных ресурсов. Информацию о MCI можно найти, к примеру, в [К.4] . Аналогичным образом обстояли дела и с получением информации от устройств ввода: клавиатуры, мыши и джойстиков.
  • OpenGL является кросплатформенным API, не привязанным к операционной системе. В результате в нем отсутствуют какие-либо средства для создания окон, загрузки текстур и моделей из файлов и т.д., так как реализация данной функциональности неминуемо бы ограничила переносимость этого API. Подобные особенности несколько усложняют разработку приложений, предназначенных исключительно для платформы Windows.
  • OpenGL является полностью открытым API, не имеющим единого хозяина. Развитие OpenGL координируется наблюдательным комитетом по архитектуре ( ARB ), в который входят ведущие лидеры индустрии, такие как Intel, Microsoft, AMD, NVIDIA, SGI, 3D Labs, Evans Sutherland и т.д. Такое число участников зачастую приводит к конфликтам внутри комитета, последствия которых хорошо описаны в известной басне И.А. Крылова "Лебедь, рак и щука".
  • Кроме того, использование открытого API, пускай и лучшего в индустрии, в качестве одного из краеугольных компонентов, мягко говоря, не отвечает интересам политики Microsoft.

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

    DirectX

    Первым игровым API, разработанным Microsoft, стал WinG. Это был достаточно примитивный API предназначенный для работы исключительно с двухмерной графикой реального времени в операционных системах Windows 3.1 и Windows 95. Видеорежимы с количеством цветов более 256 не поддерживались. Единственным преимуществом WinG была более высокая производительность, чем у GDI. Впрочем, на фоне DOS и OpenGL 1.1 оно выглядело весьма спорным.

    После неудачи с WinG стало ясно, что Microsoft вряд ли сможет в одиночку в короткие сроки разработать конкурентоспособный API для программирования графических приложений реального времени. В результате было принято решение купить британскую компанию RenderMorfics и перенести еe библиотеку Reality Lab на платформу Windows. Так появился Game SDK, позже переименованный в DirectX.

    Из-за сжатых сроков Microsoft не смогла создать на основе Reality Lab полноценный масштабируемый API с заделом на будущее, в результате чего на рынке началась настоящая чехарда версий DirectX, для получения представления о масштабах которой достаточно посмотреть на частоту появления новый версий DirectX (таблица 1.1). Нетрудно заметить, что каждый год выходило не менее одной версии DirectX, причeм новая версия содержала множество кардинальных изменений, в результате чего оказывалась несовместимой с более старой версией. Положение стабилизировалось лишь в 2002-м году с выходом 9-й версии DirectX. С тех пор были выпущены лишь три новых редакции DirectX с незначительными изменениями, после чего Microsoft перешла к выпуску обновлений к DirectX, устраняющих мелкие недочеты, в темпе одно обновление каждые два месяца.

    Примечание

    Любопытно, что графическая подсистема DirectX смогла достичь функциональность OpenGL 1.1 (существовавшего ещe до появления DirectX ) лишь к 7-й версии. Впрочем, к 9-й версии возможности OpenGL и DirectX сравнялись, после чего наметилась тенденция к технологическому отставанию OpenGL от DirectX.

    Даты выхода версий DirectX
    Версия DirectX Дата выхода
    DirectX 1.0 1995
    DirectX 2.0 1996
    DirectX 3.0 / 3.0a 1996
    DirectX 5.0 / 5.1 / 5.2 1997-1998
    DirectX 6.0 / 6.1 1998-1999
    DirectX 7.0 / 7.0a / 7.1 1999
    DirectX 8.0 / 8.1 2000-2001
    DirectX 9.0 / 9.0a / 9.0b / 9.0c + Updates 2002-2006

    Что представляет собой DirectX? Это высокопроизводительная мультимедийная библиотека для программирования приложений требовательных к производительности видеоподсистемы, аудиосистемы и системы ввода-вывода компьютера. В основе DirectX лежит набор COMComponent Object Model – модель компонентных объектов. Определяет стандарты интерфейсов API и бинарные стандарты для связи объектов, не зависящих от языка программирования. Каждый объект COM имеет один или несколько интерфейсов, представляющих собой таблицы функций, связанных с этим объектом. В продаже и свободном доступе имеется множество литературы, посвященной COM, начиная с [К.9] и заканчивая MSDN -интерфейсов, предоставляющих программисту доступ к аппаратному обеспечению компьютера. Эти интерфейсы разработаны Microsoft в тесном сотрудничестве с ведущими производителями аппаратных устройств, таких как Intel, ATI, NVIDIA,Creative и т.д. Поэтому интерфейсы DirectX очень близки к современному аппаратному обеспечению и фактически связаны с аппаратным обеспечением через тонкую прослойку драйверов, минуя стандартные интерфейсы Win32, такие как GDI и MCI.

    В DirectX 9 интерфейсы сгруппированы в три специализированных компонентаВ действительности в составе DirectX имеется ещe четыре компонента: DirectDraw (работа с двухмерной графикой), DirectMusic (проигрывание фоновой музыки), DirectPlay (работа с сетью) и DirectShow (проигрывание видеофайлов). Однако эти компоненты не рекомендуются к использованию, так как они объявлены устаревшими и по видимости будут исключены из будущих версий DirectX (рисунок 1.1):

  • DirectX Graphics, отвечающий за работу с двухмерной и трехмерной графикой.
  • DirectInput, предназначенный для работы с устройствами ввода: мышью, клавиатуры, джойстиками с обратной связью и т.п.
  • DirectSound, используемый для работы со звуковым оборудованием: звуковыми картами, в том числе и с поддержкой трeхмерного звука, MIDI -синтезаторами и т.п.
  • (рис 1.1) Упрощенная схема взаимодействия приложения с устройствами при использовании DirectX

    Managed DirectX

    После выхода платформы .NET Framework встал вопрос об использовании трехмерной графики в приложениях, написанных на управляемых языках вроде C#. Дело в том, что COM -интерфейсы компонентов DirectX проектировались в расчeте на использование в программах на языке C++, в результате чего они активно используют специфические возможности C++, к примеру, указатели. Поэтому, хотя язык C# и позволяет использовать COM -компоненты, применение интерфейсов DirectX в программах на C# сопряжено с рядом проблем: применение же указателей в C# не приветствуется и является плохим тоном программирования на платформе .NET, указатели могут использоваться только в unsafe-блоках, что затрудняет чтение программы и повышает вероятность ошибок. Вдобавок, COM -компоненты не могут использовать преимущества инфраструктуры .NET такие как автоматическая сборка мусора и обработка ошибок с использованием исключений.

    Чтобы облегчить жизнь разработчикам приложений для платформы .NET, Microsoft включила в состав 9-й версии DirectX надстройку над COM -интерфейсами DirectX: Managed DirectX. В октябре 2005 года была анонсирована вторая версия Managed DirectX, которая была фактически переписана с нуля с учeтом новых возможностей платформы .NET 2.0, в частности, Managed DirectX стал активно использовать обобщенные ( Generic ) классы.

    Managed DirectX 2.0 является тонкой надстройкой над DirectX, размещенной в сборке Microsoft.DirectX.dll. Сборка Microsoft.DirectX.dll удовлетворяет всем требованиям платформы .NET 2.0 и не привязана к конкретному языку программирования. Соответственно она может с одинаковой легкостью использоваться в любой .NET -совместимом языке программирования вроде Microsoft Visual Basic, Microsoft Visual C#, Microsoft Visual J#, Iron Python и т.п. Как и оригинальный DirectX, Managed DirectX состоит из трeх компонентов: Direct3D Graphics, DirectInput и DirectSound. Каждому компоненту соответствует одно или несколько пространств имeн (таблица 1.2), содержащих классы и структуры данного компонента. При этом хорошо прослеживается соответствие между классами DirectX и соответствующими COM -интерфейсами неуправляемого DirectX К примеру, COM -интерфейсу IDirect3D9 соответствует брат-близнец класс Microsoft.DirectX.Direct3D.Device, интерфейсу IDirect3DTexture9 – класс Microsoft.DirectX.Direct3D.Texture итак далее. Более того, при должной сноровке при изучении Managed DirectX вполне можно пользоваться документацией по C++ и наоборотЭто очень важный нюанс, так как в настоящее время классический DirectX содержит гораздо более подробную документацию .

    Таблица 1.2. Пространства имeн Managed DirectX 2.0
    Пространство имeн Соответствующий компонент DirectX Описание
    Microsoft.DirectX Используется всеми компонентами DirectX Содержит базовый набор классов, общий для всех компонентов: векторы, матрицы, кватернионы и т.п.
    Microsoft.DirectX.Generic Используется всеми компонентами DirectX В этом пространстве имeн расположены все обобщенные классы
    Microsoft.DirectX.Direct3D DirectX Graphics Отвечает за работу с 3D графикой
    Microsoft.DirectX.Direct3D.CustomVertex DirectX Graphics Содержит структуры типовых форматов вершин
    Microsoft.DirectX.DirectInput DirectInput Работа с устройствами ввода
    Microsoft.DirectX.XInput DirectInput Работа с устройствами ввода игровой приставки XBOX
    Microsoft.DirectX.DirectSound DirectSound Работа со звуком, в том числе и трeхмерным

    Однако Beta версия Managed DirectX 2.0 не была доведена до Release, став частью значительно более амбициозного проекта XNA Framework.

    XNA Framework

    В 2005-м году в продажу поступила игровая приставка Microsoft следующего поколения – XBOX 360. На этот раз Microsoft решила отказаться от использования процессоров привычной архитектуры x86 в пользу процессора PowerPC, не совместимого с x86. Сама архитектура игровой приставки так же значительно отличалась от персонального компьютера, работающего под управлением операционной системы Windows. Таким образом, впервые в истории у Microsoft оказалось две несовместимых игровых платформы: Windows и XBOX 360. Это обстоятельство ощутимо осложнило жизнь разработчикам игр, так как написание приложения с поддержкой обоих платформ фактически сводится к написанию отдельных приложений для каждой платформы, что под силу лишь достаточно крупным конторам.

    Подобная изоляция двух платформ не устраивала Microsoft, поэтому возникла необходимость создания инструментария позволяющего небольшим конторам и начинающим разработчикам создавать кроссплатформенные приложения, работающие как на платформе Windows, так и на XBOX 360. В качестве основы было решено использовать платформу .NET: как известно, .NET -приложения компилируются в промежуточный язык IL, а финальная компиляция в машинный код происходит только при запуске приложения на конкретной системе. Таким образом, .NET -приложению, в общем-то, безразлично, какой процессор в данный момент установлен в системе.

    Но на практике все оказалось несколько сложнее:

  • .NET Framework содержит мощную библиотеку классов на все случаи жизни, включая разработку приложений баз данных, web -сервисов, web -сайтов. Разумеется, данная функциональность является, мягко говоря, несколько избыточной для игровой приставки. Кроме того, ряд классов .NET Framework сильно привязаны к операционной системе Windows (пространства имен System.Windows.Forms, System.Drawings ), а перенос Windows на игровую приставку является весьма сомнительной затеей.
  • Managed DirectX, является достаточно тонкой настройкой над DirectX. А так как DirectX – это один из компонентов платформы Windows, Managed DirectX автоматически оказывается непереносимым API, привязанным к платформе Windows. Кроме того, классы и структуры Managed DirectX активно используют функциональность из пространств имен System.Windows.Forms и System.Drawing.
  • Первая проблема была решена путeм реализации на XBOX лишь подмножества классов .NET Framework, известного как .NET Compact Framework. Для решения второй проблемы был разработан XNA Framework -высокопроизводительный кроссплатформенный API для разработки графический приложений для платформ Windows и XBOX 360.

    Условно все компоненты XNA Framework можно разделить на 4 уровня абстракции (рисунок 1.2):

  • Platform (Платформа) - самый нижний уровень, содержащий платформо-зависимые API, такие как неуправляемый DirectX. В подавляющем большинстве случаев приложение может ничего не знать о существовании этого уровня, используя компоненты более высоких уровней абстракции. Более того, прямое обращение уровню Platform неминуемо сузит диапазон платформ, поддерживаемых приложением: не исключено, что Microsoft в будущем добавит поддержку XNA Framework и в операционные системы для карманных устройств.
  • Core Framework (Основной Каркас) - нижний платформо-независимый уровень XNA, обеспечивающий базовую функциональность. Размещается в сборке Microsoft.Xna.Framework.dll и содержит 5 компонентов: Graphics (работа с графикой), Audio (работа со звуком), Input (работа с устройствами ввода-вывода), Math (математические расчеты), Storage (работа с файловой системой). Классы и структуры каждого компонента сгруппированы в пространства имен (таблица 1.3). На платформе Windows первые три компонента ( Graphics, Audio, Input ) являются надстройками над DirectX, а компонент Storage - надстройкой над классами .NET Framework для работы с файловой системой. Однако следует всегда помнить о том, что на других платформах всe может обстоять совершенно иначе.
  • Extended Framework (расширенный каркас) - набор высокоуровневых классов, решающих типовые задачи, встающие перед разработчиком игр: инициализация графического устройства, организация цикла обработки сообщений, экспорт моделей и текстур из графических редакторов. По сути Extended Framework можно считать универсальным игровым движком ( Game Engine ) начального уровня. Размещается в сборке Microsoft.Xna.Framework.Game.dll.
  • Game - собственно приложение пользователя, то есть наши с вами программы. К слову, в комплект XNA входит несколько простых игр ( Starter Kits ), которые можно использовать в качестве заготовок для своих приложений.
  • (рис 1.2) Уровни XNA Framework
    Таблица 1.3. Пространства имен сборки Microsoft.Xna.Framework.dll
    Пространство имен Соответствующий компонент XNA Назначение
    Microsoft.Xna.Framework Math Математические расчеты: матричная алгебра, аналитическая геометрия, проверка столкновений и т.д. В Managed DirectX эта функциональность (в урезанном виде) реализовывалась посредством библиотеки D3DX, являющейся частью DirectX. XNA Framework выполняет математические расчеты собственными средствами, что в некоторых случаях несколько повышает производительность благодаря отсутствию накладных расходов взаимодействия с COM.
    Microsoft.Xna.Framework.Graphics Работа с графикой
    Microsoft.Xna.Framework.Graphics.PackedVector Graphics Работа с упакованными векторами. Примером упакованного вектора является 32-х битное число, содержащее информацию о яркости красной, синей и зеленой компонентах цвета
    Microsoft.Xna.Framework.Audio Audio Работа со звуком
    Microsoft.Xna.Framework.Input Input Работа с устройствами ввода (клавиатура, мышь, джойстики).
    Microsoft.Xna.Framework.Storage Storage Работа с файловой системой текущей платформы: загрузка и сохранение настроек приложения, "сохраненных игр" ( Save Games ) и т.д.

    Ничего страшного, если у вас на первых порах будет рябить в глазах от обилия компонентов. По мере изучения XNA Framework всe встанет на свои места. В первой лекции мы познакомимся с некоторыми классами пространства имен Microsoft.Xna.Framework.Graphics, и научимся использовать их для визуализации относительно простых двухмерных изображений.

    1.1. Создание простейшего приложения, использующего XNA Framework.

    (рис 1.3) Наше первое приложение(Ex01), использующее XNA. Graphic User Interface – графический пользовательский интерфейс

    Как известно, лучший способ получить представление о новой технологии – написать с еe помощью простейшее приложение. Так мы и поступим. Наше первое приложение, использующее XNA Framework, будет просто закрашивать форму синим цветом (рисунок 1.3). Для создания GUIGraphic User Interface – графический пользовательский интерфейс -интерфейса мы воспользуемся библиотекой Windows Forms, являющуюся стандартом для платформы .NET.

    Для начала запустите Microsoft Visual Studio 2005 и создайте новый проект GUI –приложения для платформы Windows (File | New | Project...). В раскрывшемся окне выберите Visual C# | Windows | Windows Application, введите название приложения, снимите флажок Create directory for Solution и нажмите Ok. Переименуйте файл формы из Form1.cs в MainForm.cs.

    Следующий шаг – подключение сборки Microsoft.Xna.Framework.dll, содержащий компоненты слоя Core Framework, включая необходимый нам компонент Graphics.Для подключения сборки щeлкните правой кнопкой мыши на узле Reference в окне Solution Explorer и выберите в контекстном меню пункт Add Reference... (рисунок 1.4). В открывшемся окне выберете сборку Microsoft.Xna.Framework и нажмите кнопку Ok (рисунок 1.5).

    (рис 1.5) Вкладка Solution Explorer(рис 1.4) Окно Add Reference

    Теперь мы можем приступать к собственно написанию программы. Откройте окно редактирования исходного кода, щелкнув правой кнопкой мыши по форме и выбрав пункт View Code контекстного меню (либо нажав на кнопке View Code в верхней части окна Solution Explorer ). Так как мы будем активно использовать классы из пространства имен Microsoft.Xna.Framework.Graphics, было бы логично добавить в начало программы следующую строку:

    using Microsoft.Xna.Framework.Graphics;

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

    public GraphicsDevice(GraphicsAdapter adapter, DeviceType deviceType, 
    IntPtr renderWindowHandle, CreateOptions creationOptions, params PresentationParameters[] 
    presentationParameters);

    где

  • adapter - экземпляр класса GraphicsAdapter, соответствующей используемой видеокарте (многие современные компьютеры содержат две и более видеокарты). Для указания видеокарты по умолчанию достаточно передать в качестве данного параметра значение статического свойства GraphicsAdapter.DefaultAdapter.
  • deviceType - тип устройства, задаваемый с использованием перечислимого типа DeviceType (таблица 1.4). На практике обычно используется значение DeviceType .Hardware.
  • renderWindowHandle - дескриптор окна или элемента управления, который будет использоваться для вывода информации.
  • creationOptions - набор битовых флагов перечислимого типа CreateOptions, задающих режим работы устройства (таблица 1.5). В нашем случае мы будем использовать режимы CreateOptions.SoftwareVertexProcessing и CreateOptions.SingleThreaded.
  • PresentationParameters - набор структур PresentationParameters, описывающих представление данных на экране монитора. Каждому монитору соответствует своя структура PresentationParameters. Так как наши приложение будет осуществлять вывод только на один монитор, мы ограничимся одной структурой PresentationParameters. В таблице 1.6 приведено описание некоторых свойств структуры PresentationParameters.
  • Если попытка создания устройства заканчивается неудачей, конструктор генерирует исключение. Подобная ситуация возникает, к примеру, при попытке создания устройства с использованием флага CreateOptions.HardwareVertexProcessing на видеокарте, не имеющей аппаратных вершинных процессоров. Примерами таких видеокарт являются Intel GMA 900 и Intel GMA950, интегрированные в чипсеты i915G и i945G соответственно. Так как флаг HardwareVertexProcessing влияют исключительно на выполнение вершинных шейдеров, в то время как примеры этой лекции не используют эту функциональность, применение данного флага некоим образом не повлияет на производительность наших приложений и лишь неоправданно увеличит требования к видеокарте. Поэтому в примерах первой лекции курса мы ограничимся использованием лишь флага SoftwareVertexProcessing.

    Таблица 1.4. Значения перечислимого типа DeviceType
    Значение Описание
    Hardware Аппаратное устройство
    Reference Устройство эмулируется средствами DirectX SDK. Обычно используется для эмуляции функциональности, не поддерживаемой текущей видеокартой (например, эмуляция пиксельных шейдеровОсновы вершинные и пиксельных шейдеров будут рассмотрены в лекции 2.3 на GeForce2). Правда, такую эмуляцию реально использовать лишь в отладочных целях, так как производительность приложений в этом режиме редко превышает один кадр в секунду.
    NullReference Нуль-устройство, игнорирующее все поступающие команды (наподобие устройства NUL в MS-DOS и Windows ). Может использоваться для оценки производительности приложения на бесконечно быстрой видеокарте.
    Значения перечислимого типа CreateFlags
    Значение Описание
    HardwareVertexProcessing Аппаратная обработка вершин средствами GPUGraphic Processor Unit – графический процессор, установленный на видеокарте
    SoftwareVertexProcessing Программная обработка вершин средствами CPU
    MixedVertexProcessing Смешанная обработка вершин. Позволяет приложению самостоятельно переключаться между программной и аппаратной обработкой вершин. Полезна, к примеру, при использовании вершинных шейдеров версии 2.0 на GeForce3, который аппаратно поддерживает только вершинные шейдеры версии 1.1. В этом случае вершинные шейдеры версии 1.1 можно выполнять аппаратно, а версии 2.0 – в режиме программной эмуляции
    NoWindowChanges Устройство не будет автоматически восстанавливаться после потери фокуса окном и т.д. Все эти действия ложатся на плечи программиста. Данное значение применяется очень редко
    SingleThreaded Увеличивает производительность за счет отключения критических секций, позволяющих одновременно обращаться к устройству из нескольких потоков. Рекомендуется всегда по возможности использовать этот флаг.
    SinglePrecision Переключает математический сопроцессор в режим пониженной точности. Все вычисления с плавающей точкой, включая использующие тип double, будут выполнять с точностью 7 знаков. Более подробная информация об этом режиме приведена во врезке
    Некоторые свойства структуры PresentationParameters
    Поле Описание
    bool IsFullScreen При выводе на поверхность компонента или формы этому свойству необходимо присвоить значение false. Если же приложение является полноэкранным, то используется значение true
    int BackBufferCount Задает количество вспомогательных буферов, используемых для борьбы с эффектом мерцания при смене кадров. При использовании одного вторичного буфера изображение сначала рисуется во вспомогательном буфере, после чего уже готовое изображение копируется в экранный буфер. Этот процесс называется двойной буферизацией. Буферизация, использующая два вспомогательных буфера, называется тройной. Более подробно различные типы буферизации будут рассмотрены в соответствующих разделах курса. А пока мы будем использовать двойную буферизацию, присваивая полю BackBufferCount значение 1.
    int BackBufferWidth Ширина вспомогательных буферов в пикселях. Если этот параметр равен 0, то конструктор метода Device рассчитывает его автоматически, полагая равным ширине клиентской области окнаЧасть окна, используемая приложением для вывода информации. В клиентскую область окна не входят заголовок окна, рамки по краям окна и т.д
    int BackBufferHeight Высота вспомогательных буферов в пикселях. Если этот параметр равен 0, то конструктор метода Device рассчитывает его автоматически, полагая равным высоте клиентской области окна
    Direct3D.SwapEffect SwapEffect Указывает, каким образом осуществляется переключение буферов при двойной и тройной буферизации. Подавляющее большинство программ присваивают этому полю значение SwapEffect.Discard – в этом случае видеокарта сама выбирает режим переключения буфера, наиболее подходящий в конкретной ситуации.
    Точность вычислений с плавающей точкой процессоров архитектуры x86

    Блок вычислений с плавающей точкой ( FPU ) процессоров x86До появления i486DX этот блок располагался в отдельной микросхеме, называемой математическим сопроцессором (8087, 80287, 80387) содержит восемь 80-ти битных регистров общего назначения, используемых для хранения операндов и результатов вычислений. Иными словами, независимо от используемых типов данных сопроцессор всегда оперирует с 80-ти битным форматом с плавающей точкой, известным как extended double. Если операнды имеют меньшую разрядность (например, используется тип float ), то они автоматически конвертируются в 80-ти битный формат, а результат перед копированием в память переводится обратно в 32-х битный формат float. Так как подобная точность вычислений оказывается излишней, в одном из управляющих регистров процессора ( CW ) имеются два бита ( PC ), управляющие точностью вычислений. В зависимости от значения битов, задающихся при запуске приложения, вычисления выдадутся с 7-ю, 16-ю или 19-ю значащими цифрами. Ещe раз хочу подчеркнуть важную деталь. Формат чисел в регистрах всегда остаeтся 80-ти битным, просто с понижением точности младшие разряды числа с плавающей точкой могут содержать недостоверную информацию.

    Два бита PC регистра CW устанавливаются при запуске потока ( thread ) и, как правило, не меняются в процессе выполнения приложения. В то же время, параметр CreateOptions.SinglePrecision приказывает конструктору класса GraphicsDevice изменить служебный регистр CW таким образом, чтобы установить внутри сопроцессора точность вычислений 7 знаков. В результате, после вызова данного метода вычисления, включая использующие типы double, будут выполняться с 7-ю значащими знаками. Правда существуют и исключения: так, к примеру, биты PC регистра CW оказывают влияние на точность сложения, вычитания, умножения, деления и вычисления квадратного корня, но совершенно не затрагивают точность команд вычисления синуса и косинуса.

    В общем, здесь очень много различных тонкостей, но разбираться в них, в общем-то, не нужно. Достаточно запомнить, что флаг CreateOptions.SinglePrecision таит много подводных камней, поэтому применять его стоит только при реальной необходимости поднять производительно математической подсистемы любой ценой. В остальных случаях вы лишь повысите вероятность возникновения различных трудно обнаруживаемых ошибок. Ведь никто не сможет гарантировать, что одна из используемых вами библиотек не начнeт вести себя как-то странно, столкнувшись с аномально низкой точностью вычислений, использующих типы double. Например, пониженная точность может очень негативно сказаться на качестве генератора псевдослучайных чисел.

    Итак, для создания графического устройства мы должны объявить в тексте класса формы поле класса

    GraphicsDevice:
    GraphicsDevice device=null;

    Затем в обработчик события Load формы необходимо вставить код создания нового экземпляра класса GraphicsDevice c заданными параметрами листинг 1.1).

    private void MainForm_Load(object sender, EventArgs e)
    {
    // Инициализируем все поля структуры presentParams значениями по умолчанию
    PresentationParameters presentParams = new PresentationParameters(); 
    // Мы будем осуществлять вывод на поверхность формы, то есть в оконном режиме
    presentParams.IsFullScreen = false; 
    // Включаем двойную буферизацию
    presentParams.BackBufferCount = 1; 
    // Переключение буферов должно осуществляться с максимальной эффективностью
    presentParams.SwapEffect = SwapEffect.Discard; 
    // Задаем ширину и высоту клиентской области окна. Если присвоить этим полям значение 0 (что 
    // и происходит по умолчанию), то конструктор класса GraphivsDevice автоматически рассчитает 
    // значение этих полей и занесeт их в структуру presentParams. Поэтому эти две строки, в 
    // принципе, можно и опустить.
    presentParams.BackBufferWidth = ClientSize.Width;
    presentParams.BackBufferHeight = ClientSize.Height;
    // Создаем новое устройство, обладающее следующими характеристиками:
    // - Устройство будет использовать видеоадаптер по умолчанию
    // - Устройство будет аппаратным
    // - Вывод будет осуществляться на поверхность текущей формы
    // – Обработка вершин будет осуществляться средствами GPU
    // - Представление данных на экране задаeтся структурой 
    presentParams (см. выше)
    // – Доступ к устройству возможен только из одного потока приложения
    device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, 
    DeviceType.Hardware, Handle, CreateOptions.SoftwareVertexProcessing 
    | CreateOptions.SingleThreaded, presentParams); 
    }

    Создав объект устройства, можно приступать к реализации закраски формы синим цветом. Для этого воспользуемся методом Clear класса GraphicsDevice, который очищает форму путeм закраски еe заданным цветом:

    public void Clear(ClearOptions options, Color color, float depth, 
    int stencil);

    где

  • options - набор битовых флагов, указывающих какие буферы необходимо очистить. Для очистки экранного буфера используется флаг ClearOptions.Target. Остальные флаги ClearOptions.DepthBuffer и ClearOptions.Stencil, используемые для очистки соответственно буфера глубины и буфера шаблона, которые будут рассмотрены в следующих лекциях.
  • color - цвет, которым будет закрашен экран. Задаeтся с использованием структуры Microsoft.Xna.Framework.Graphics.Color, являющейся функциональным аналогом структуры System.Drawing.Color. Появление такого брата-близнеца обусловлено необходимостью сделать XNA Framework независимым от функциональности платформы Windows.
  • depth - значение, которым будет "закрашен" буфер глубины.
  • stencil - значение, которым будет заполнен буфер шаблона.
  • Вызов метода Clear необходимо вставить в обработчик события Paint, вызываемый каждый раз при необходимости перерисовки содержимого формы:

    private void MainForm Paint(object sender, PaintEventArgs e) 
    {
    device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
    Graphics.Color.CornflowerBlue, 
    0.0f, 0) ; 
    }

    Обратите внимание на использование полного имени структуры Microsoft.Xna.Framework.Graphics.Color с указанием пространства имен. Если это не сделать, возникнет конфликт с одноименной структурой из пространства имен System.Drawing.

    Класс GraphicsDevice имеет и более простую перегрузку ( override ) метода, предназначенную для очистки исключительно экранного буфера:

    public void Clear(Color color);

    где

    color - цвет, которым заполняется весь экран

    Использование данного варианта перегрузки метода позволяет несколько упростить код приложения:

    private void MainFormPaint(object sender, PaintEventArgs e)
    {
    device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue); 
    }

    По окончанию работы приложение должно удалить графическое устройство при помощи метода Dispose. Для этой цели идеально подходит обработчик события FormClosed (листинг 1.2).

    private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
    {
    //  Если устройство существует
    if (device != null)
    { //  Удаляем (освобождаем) устройство
    device.Dispose(); 
    // На всякий случай присваиваем ссылке на устройство значение 
    null device = null;
    }
    }

    Если вы забудете удалить объект устройства, .NET самостоятельно попытается вызвать метод Dispose экземпляра класса GraphicsDevice в процессе сборки мусора (если быть более точным, сборщик мусора вызывает метод Finalize, который довольно часто реализует вызов метода Dispose ). Но здесь имеется один нюанс. Как известно, сборщик мусора для вызова методов Finalize удаляемых объектов создаeт отдельный поток, в то время как на платформе Windows устройство Direct3D имеет право удалить только поток, создавший это устройство. Соответственно, деструктор объекта GraphicsDevice, вызываемый из параллельного потока, не сможет корректно удалить устройство Direct3D.

    Примечание

    Даже если вы не укажете при создании устройства флаг CreateOptions.SingleThreaded, сборщик мусора всe равно не сможет корректно удалить объект.

    Вроде бы всe. Давайте попробуем запустить полученное приложение на выполнение (клавиша F5 ). Не смотря на то, что метод Clear вызывается при каждой перерисовке окна (в этом легко убедится, установив точку останова на строку с вызовом этого метода при помощи клавиши F9 ) , на внешнем виде формы это никак не отражается. Интересно, с чем это может связано?

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

    void Present()

    Добавьте вызов этого метода в конец обработчика события Paint и снова запустите программу на выполнение - на экране появится окно, закрашенное синим цветом, что и требовалось. Исходный код готового приложения находится в каталоге Ch01\Ex01.

    1.2. Визуализация шахматной доски

    Одна из перегрузок метода GraphicsDevice.Clear позволяет очищать не весь экран целиком, а лишь заданную прямоугольную область формы:

    public void Clear(ClearOptions options, Color color, 
    float depth, int stencil, Rectangle[] rect);

    где

    rect - массив структур Microsoft.Xna.Framework.Rectangle, задающих прямоугольные области экрана, которые должны быть очищены. Области экрана задаются в оконных координатах формы - начало координат расположено в левом верхнем углу. Структура Microsoft.Xna.Framework.Rectangle является близнецом одноименной структуры из пространства имен System.Drawing, и используется во избежание привязки XNA Framework к платформе Windows.

    Примечание

    Структура Rectangle объявлена в пространстве имен Microsoft.Xna.Framework, так как он используется многими классами XNA Framework, в том числе и не из пространства имен Microsoft.Xna.Framework.Graphics.

    К примеру, следующий фрагмент кода нарисует в центре экрана зелeный прямоугольник на синем фоне (рисунок 1.6):

    // Закрашиваем экран синим цветом
    device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
    // Создаeм массив с координатами областей экрана, которые необходимо закрасить. Нам
    // нужна всего одна область
    Microsoft.Xna.Framework.Rectangle[] rect = new Microsoft.Xna.
    Framework.Rectangle[1];
    // Задаeм координаты области экрана, расположенной в центре экрана и занимающей 25%
    // площади экрана
    rect[0] = new Microsoft.Xna.Framework.Rectangle(ClientSize.Width/4,
    ClientSize.Height/4, ClientSize.Width/2, ClientSize.Height/2);
    // Закрашиваем эту область зелeным цветом
    device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
    Graphics.Color.Green, 0.0f,
    4> 0, rect);
    (рис 1.6) Зелeный квадрат на синем фоне, нарисованный с использованием метода Clear

    В принципе при грамотном использовании только одного метода Clear можно получать довольно интересные изображения. К примеру, никто не мешает нам нарисовать шахматную доску (рисунок 1.7). Для этого необходимо очистить экран белым цветом, затем создать массив областей экрана, соответствующих клеткам доски коричневого цвета и ещe раз очистить экран, но уже коричневым цветом (листинг 1.4).

    (рис 1.4) Шахматная доска, нарисованная с использованием метода Clear(рис 1.7) // Полный код приложения находится в каталоге Examples\Ch01\Ex02
    private void MainFormPaint(object sender, PaintEventArgs e)
    {
    // Очищаем экран белым цветом
    device.Clear(Microsoft.Xna.Framework.Graphics.Color. WhiteSmoke);
     // Создаeм массив областей закраски, соответствующих коричневым клеткам
    Microsoft.Xna.Framework.Rectangle[] rects = 
    new Microsoft.Xna.Framework.Rectangle[32] ;
    int k = 0; 
    // Перебираем коричневые клетки шахматной доски for 
    (int j = 0; j < 8; j++)
    for (int i = j % 2; i < 8; i += 2)
     {
    // Заносим в массив координаты очередной клетки
    rects[k] = new Microsoft.Xna.Framework.Rectangle(i * 
    ClientSize.Width / 8, 
    4 j * ClientSize.Height / 8, ClientSize.Width / 8, 
    ClientSize.Height / 8);
    k++;
    }
    // Закрашиваем все области из массива rects коричневым цветом
    device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
    Graphics.Color.Brown, 0.0f, 0, 
    4> rects);
    device.Present();
     }

    1.2.1. Конфигурирование DirectX для отладки приложени

    Как вы помните, на платформе Windows XNA Framework в некотором роде является высокоуровневой надстройкой над DirectX. Соответственно, на платформе Windows подавляющее большинство вызовов методов XNA Framework так или иначе транслируется в вызовы методов DirectX. В большинстве случаев это обстоятельство можно полностью игнорировать. Тем не менее, при возникновении различных "аномалий" в приложении обращение к нижележащему уровню может помочь быстро решить проблему.

    Конфигурирование DirectX осуществляется при помощи утилиты DirectX, запускаемой командой Start | All Programs | Microsoft DirectX SDK | DirectX Utilities | DirectX Control Panel. Внешний вид этой утилиты изображeн на рисунке 1.8. Как видно, данная утилита представляет собой обычное диалоговое окно с набором вкладок, отвечающих за настройку различных компонентов DirectX. Рассмотрим наиболее важные из них.

    (рис 1.8) Внешний вид утилиты конфигурирования DirectX. Открыта вкладка Direct3D

    Вкладка Direct3D предназначена для настройки компонента Direct3D Graphics. Как правило, эта вкладка используется для переключения между отладочной и "обычной" версией Direct3D при помощи переключателей Use Debug Version of Direct3D и Use Retail Version of Direct3D соответственно (расположены в группе Debug/Retail D3D Runtime ). Отладочная версия Direct3D Graphics проводит дополнительную проверку правильности параметров передаваемых классам Direct3D Graphics и правильности выполнения этих методов. Информация о различных подозрительных ситуациях и ошибках передаeтся в отладчик (например, в отладчик Visual Studio 2005 ).

    При разработке и отладке приложений рекомендуется всегда использовать отладочную версию Direct3D Graphics. Так же полезно установить ползунок Debug Output Level, отвечающий за подробность отладочной информации в крайнее правое положение, чтобы получать информацию о любых подозрительных ситуациях. Ведь согласно "эффекту бабочки", даже самый безобидный на первый взгляд недочeт может привести к каскаду трудноуловимых ошибок.

    В группе Debugging желательно включить следующие флажки: Maximum Validation (максимальная проверка корректности параметров, передаваемых классам Direct3D Graphics ), Enable Shader Debugging (отладка шейдеров) и Break on Memory Leaks (обнаружение утечек памяти).

    Внимание

    Отладочная ( Debug ) версия DirectX Graphics значительно медленнее обычной ( Retail ) версии. Поэтому не забывайте отключать отладочную версию DirectX по завершению отладки. В противном случае вы рискуете столкнуться с аномально низкой производительностью трeхмерных игр и аналогичных приложений. Для того чтобы переключиться в нормальный режим, достаточно просто включить радиокнопку Use Retail Version of DirectX – остальные опции вроде Debug Output Level не оказывают никакого влияния на обычную версию Direct3D Graphics.

    Debug View

    По умолчанию Visual Studio 2005 Pro не отображает сообщения от отладочной версии DirectX, а в бесплатной версии Visual C# 2005 Express подобная функциональность не предусмотрена в принципе. Поэтому я включил в example.zip бесплатную программу Марка Руссиновича Debug View, расположенную в каталоге \Tools\DebugView. Скопируйте еe на локальный жесткий диск компьютера и запустите файл DebugView.exe. На экране появится окно следующего вида (рисунок 1.9). Наибольший интерес для нас представляет центральная часть окна, в которой отображают отладочные сообщения от всех приложений, выполняющихся в данный момент на компьютере. Если вы поработаете некоторое время на компьютере при запущенной утилите Debug View, то наверняка заметите множество отладочных сообщений от разнообразных приложений.

    (рис 1.9) Приложение Debug View

    Откройте панель управления DirectX, и включите отладочную версию DirectX. Запустите на выполнение приложение, рисующую шахматную доску (пример Ch01\Ex02 ), поработайте с ним некоторое время, после чего завершите. Тем временем в окне Debug Info появится информация следующего вида:

    // Библиотека Direct3D загружается в адресное пространство 
    нашего приложения Direct3D9: :====> ENTER: DLLMAIN(041dd6e0): 
    Process Attach: 0000041c, tid=000016a8 Direct3D9: :====> EXIT: 
    DLLMAIN(041dd6e0): Process Attach: 0000041c
    // Direct3D находится в отладочном режиме
    Direct3D9: (INFO) :Direct3D9 Debug Runtime selected.
    // Расширенные возможности отладки Direct3D недоступны 
    (эта функциональность доступна
    // только для DirectX-приложений, написанных на C++)
    
    D3D9 Helper: Enhanced D3DDebugging disabled; Application was 
    not compiled with D3D_DEBUG_INFO
    // Сообщение с пометкой INFO содержат разнообразную служебную 
    информацию о ходе
    // выполнения приложения. В частности следующее сообщение означает, 
    что устройство
    // находится в режиме Software Vertex Processing (Программная обработка
     вершин). Иными
    // словами, при создании графического устройства был использован флаг
    // CreateOptions.SoftwareVertexProcessing.
    Direct3D9: (INFO) :======================= Hal SWVP device selected
    Direct3D9: (INFO) :HalDevice Driver Style 9
    Direct3D9: :DoneExclusiveMode
    Direct3D9: :====> ENTER: DLLMAIN(041dd6e0): Process Detach 0000041c,
     tid=0000022c
    // Освобождение ресурсов Direct3D завершено Direct3D9: (INFO) :MemFini!
    // Завершение работы Direct3D
    Direct3D9: :====> EXIT: DLLMAIN(041dd6e0): Process Detach 0000041c

    Обратите внимание

    В столбце Time указано время поступления отладочного сообщения, что облегчает идентификацию сообщений. По умолчанию используется относительное время – за точку отсчeта берется время поступления первого события, т.е. время наступление первого события всегда равно 0.0 секунд.

    Как видно, приложение выполняется без каких-либо эксцессов. Теперь закомментируйте строку device.Dispose() в обработчике события Close() и снова запустите приложение на выполнение. На этот раз отладочные сообщения будут несколько отличаться:

    Direct3D9: :====> ENTER: DLLMAIN(042dd6e0): Process Attach: 00000298,
     tid=00000910
    Direct3D9: :====> EXIT: DLLMAIN(042dd6e0): Process Attach: 00000298
    Direct3D9: (INFO) :Direct3D9 Debug Runtime selected.
    D3D9 Helper: Enhanced D3DDebugging disabled; Application was not
     compiled with D3D_DEBUG_INFO
    Direct3D9: (INFO) :======================= Hal SWVP device selected
    Direct3D9: (INFO) :HalDevice Driver Style 9 Direct3D9: 
    :DoneExclusiveMode
    // Предупреждение. К устройству, не рассчитанному на работу в
     многопоточном режиме (в
    // конструкторе класса GraphicsDevice указан флаг 
    CreateOptions.SingleThreaded)
    // пытается обратиться другой поток.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    // Ошибка! Устройство может быть уничтожено только потоком, 
    создавшим его
    Direct3D9: (ERROR) :Final Release for a device can only 
    be called from the thread that the device was created from.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    Direct3D9: (WARN) :Device that was created without D
    3DCREATE_MULTITHREADED is being used by a thread other 
    than the creation thread.
    Direct3D9: :====> ENTER: DLLMAIN(042dd6e0): Process 
    Detach 00000298, tid=00000520
    Direct3D9: (INFO) :MemFini!
    Direct3D9: :====> EXIT: DLLMAIN(042dd6e0): Process
     Detach 00000298

    Хотя приложение и работает нормально, по отладочным сообщениям можно легко догадаться, что при завершении работы приложения сборщик мусора попытался уничтожить устройство, что естественно не удалось – ведь сборщик мусора вызывает методы Finalize в отдельном потоке, в то время как устройство Direct3D может удалить лишь тот поток, который его создал. Таким образом, отладочная версия DirectX помогла нам легко локализовать проблему.

    Единственное неудобство доставляют отличия между Direct3D и XNA Framework. К примеру, устройство Direct3D по умолчанию запускается в однопоточном режиме, а включение поддержки многопоточного режима осуществляется путем указания флага D3DCREATE_MULTITHREADED. А вот класс GraphicsDevice, напротив, по умолчанию создаeт графическое устройство с поддержкой многопоточности, а отключение данной функциональности осуществляется путем указания флага CreateOptions.SingleThreaded. Кроме того, отладочная версия Direct3D нечего не знает о .NET Framework – вместо того, чтобы сообщить о проблемах из-за удаления объекта GraphicsDevice сборщиком мусора она просто жалуется на странное поведение непонятно откуда взявшегося дополнительного потока. Впрочем, получив некоторый опыт чтения сообщений отладочной версии Direct3D, вы перестанете обращать внимание на подобные нюансы.

    Взаимодействие XNA Framework c DirectX

    Как говорилось выше, XNA Framework по сути является прослойкой между .NET и DirectX. Но насколько эта тонка прослойка и оказывает ли она существенное влияние на производительность приложения? Чтобы ответить на этот вопрос мы рассмотрим работу XNA Framework на примере метода

    Direct3D.Device.Present, декомпилировав его с использованием .NET Reflector, который находится в example.zip в каталоге Tools\NET Reflector:

    public unsafe void Present()
    {
    // Проверяет, не был ли вызван метод Dispose для данного 
    экземпляра класса
    // GraphicsDevice. Если метод Dispose уже был вызван, 
    генерируется исключение
    // ObjectDisposedException
    InternalHelper.CheckDisposed(this, (void*) this.pComPtr); 
    // Получает указатель на COM–интерфейс IDirect3DDevice9, 
    являющийся низкоуровневым 
    // аналогом класса GraphicsDevice.
    IDirect3DDevice9* devicePtr1 = this.pComPtr; 
    // Вызывает COM-метод IDirect3DDevice9::Present, 
    который выполняет переключение 
    // буферов и вывод изображения на экран. К сожалению, 
    .NET Reflector сгенерировал, 
    // мягко говоря, не самый красивый код.
    int num1 = **(((int*) devicePtr1))[0x44](devicePtr1, 0, 0, 0, 0);
    // Если метод IDirect3DDevice9::Present возвратил 
    отрицательное значение, то есть во 
    // время выполнения метода произошла ошибка 
    (в COM отрицательные значения 
    // соответствуют кодам ошибок). if (num1 < 0) 
    { 
    // Если код ошибки равен -2005530520 (соответствует потере устройства)
     if (num1 == -2005530520) 
    { 
    // Подготовка к вызову обработчика события DeviceLost 
    (если он определен) с 
    // последующим вызовом. Тема потери устройства будет 
    рассмотрена в разделе 1.2.4. EventArgs args1 = EventArgs.Empty;
    EventHandler handler1 = this.<backing_store>DeviceLost;
    if (handler1 != null)
    {
    handler1(this, args1); 
    }
    } 
    // Генерируется исключение. Класс исключения и текст сообщения 
    об ошибке определяется 
    // кодом, который вернул Com-метод IDirect3DDevice9::Present
    throw ExceptionHelper.GetExceptionFromResult(num1); 
    } 
    }

    Как видно, метод GraphicsDevice.Present содержит вызов COM -метода IDirect3DDevice9::Present плюс небольшую обвязку для взаимодействия с COM. Иными словами, на платформе Windows метод GraphicsDevice.Present по сути является обвязкой над методом . Впрочем, на других платформах всe может быть совершенно иначе.

    1.2.2. Конфигурирование проектов в Visual Studio 2005

    В этом разделе мы рассмотрим тонкости настройки свойств проекта в среде Visual Studio 2005 с учeтом специфики приложений, использующих XNA Framework. Если вы имеете большой опыт работы с Visual Studio, можете пропустить этот раздел.

    Как вы знаете, самой крупной единицей Visual Studio 2005 является решение ( Solution ), описание которого хранятся в текстовом файле формата XML с расширением .sln. Каждое решение состоит из одного или нескольких проектов: набора файлов исходного кода и ресурсов, которые будут откомпилированы в одну сборку (исполняемый файл .exe или динамическую библиотеку .dll ).Файлы с описанием проектов, использующих язык C#, имеют расширение .csproj. Файлы исходного кода C# -программы имеют расширение .cs, файлов ресурсов - .resx и т.д. В принципе для получения представления о структуре решения достаточно открыть любой проект и взглянуть на вкладку Solution Explorer (рисунок 1.4).

    Примечание

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

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

    Debug и Release.

  • Конфигурация Debug предназначена для отладки приложения. При использовании этой конфигурации приложение компилируется без использования оптимизаций, а в .exe файл добавляются отладочные символы. Благодаря этому отладчик может найти однозначное соответствие между полученным двоичным кодом и исходным текстом программы, что позволяет осуществлять пошаговое выполнение программы, просмотр промежуточных значений переменных и т.п. Кроме того, программист, используя директивы условной компиляции, может добавлять в отладочную версию кода ряд разнообразных дополнительных расширенных проверок вводимых данных.
  • Конфигурация Release применяется для построения финальной версии приложения. В этом случае компилятор применяет различные оптимизации: удаление лишних переменных, перестановка инструкций, разворачивание циклов и т.п. Это значительно повышает производительность приложения (иногда в десятки раз), однако значительно усложняет отладку, которая возможна лишь на уровне машинного кода.
  • Переключение между этими конфигурациями легко осуществляется с использованием выпадающего списка на панели инструментов Visual Studio (рисунок 1.10). Вообще, конфигурации Debug и Release отличаются между собой лишь настройками различных свойств. Теоретически, проигравшись со свойствами конфигурации Debug, вы можете легко превратить еe в функциональный аналог конфигурации Release и наоборот. Кроме того, вы можете добавить в решение некоторые специфические конфигурации вроде "Release Shareware Version", "Release Full Version" и т.д.
  • (рис 1.10) Переключение между различными конфигурациями с использованием панели инструментов

    Примечание

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

    Конфигурация Release в проектах C# по умолчанию настроена довольно оптимально и вам вряд ли придeтся еe менять. К сожалению, этого нельзя сказать о конфигурации Debug. Сейчас мы с вами это исправим. Активируйте конфигурацию Debug при помощи выпадающего списка на панели задач. Щелкните два раза на узле Properties во вкладке Solution, чтобы открыть вкладку свойств проектаЭта вкладка, по сути, является визуальным редактором файла проекта (*.cjproj и т.п.) . (рисунок 1.11).

    (рис 1.11) Свойства проекта

    Для начала щeлкните на закладку Debug и включите флажок Enable unmanaged code debugging. Из названия нетрудно догадаться, что этот флажок включает отладку неуправляемого кода. Зачем это надо? Большинство классов сборки XNA Framework являются тонкими надстройками над COM -компонентами "обычного" DirectXДанное утверждение может быть не верным для платформ, отличных от Windows . В результате при выключенной отладке неуправляемого кода Visual Studio 2005 не может получать информацию от COM -компонентов отладочной версии DirectX и, соответственно, выводить еe в окно Output.

    Попробуйте включить эту опцию в примере Ch01\Ex02 (визуализация шахматной доски) и понаблюдать в окне Output за сообщениями отладочной версии DirectX (рисунок 1.12). Отметьте ощутимо возросшее время загрузки приложения.

    (рис 1.12) Окно Output

    В целом, окно Output в сочетании с опцией Enable unmanaged code Debugging является неплохой интегрированной альтернативой утилите Debug View, хотя и не лишенной ряда недостатков – очень низкой производительности и отсутствия поддержки в Visual C# 2005 Express.

    Внимание

    Даже если вы выбрали конфигурацию Release, Visual Studio при нажатии клавиши F5 ( Start with Debugging ) все равно запускает .NET -приложение под управлением отладчика, что ощутимо снижает быстродействия. Для полного отключения отладки необходимо запустить приложение на выполнение комбинацией клавиш Ctrl + F5 ( Start without debugging ).

    Проверка переполнения

    Вторая полезная опция, Check for arithmetic overflow/underflow, находится в диалоговом окне Advanced Build Setting, открываемом при помощи кнопки Advanced Build Setting в закладке Build (рисунок 1.13). Этот флажок включает проверку целочисленного переполнения для всей программы: если в ходе вычислений результат вдруг выйдет за пределы допустимого диапазона, то программа автоматически сгенерирует исключение System.OverflowException. Эта поможет сэкономить вам много времени и нервов, при поиске трудноуловимых ошибок вроде:

    // Объявляем 16-битную переменную со знаком и присваиваем ей 32767
    short a = 32767;
    // Увеличиваем еe на 5. Из за переполнения переменная a станет 
    равна -32764, а не 32772 !!!
    a = (short) (a + 5);
    // В итоге оператор WriteLine выведет на экран -32764
    Console.WriteLine(a);
    (рис 1.13) Диалоговое окно Advanced Build Setting

    Так как проверка переполнения несколько снижает производительность программы, еe рекомендуется выключать в конфигурации Release. Если же у вас имеется потенциально опасный код, в котором может произойти переполнение, поместите его во внутрь блока checked. Например:

    checked {
    a = (short) (a + 5);
     }

    Остальные особенности конфигурирования проектов C# мы рассмотрим в следующих разделах по мере необходимости. Более подробную информацию по этой теме можно найти в [К.7], [К.8] и [К.9].

    1.2.3. Изменение размеров окна

    Запустите на выполнение нашу программу, рисующую шахматную доску ( Ex02 ) и попробуйте поизменять размеры окна. Думаю, вы быстро заметите, что с программой что-то не так. При уменьшении размеров окна шахматная доска не масштабируется, в результате чего изображение начинает выглядеть как-то странновато (рисунок 1.14). А при увеличении окна изображение шахматной доски искажается непонятным образом (рисунок 1.15).

    (рис 1.15) Некорректная реакция программы на уменьшение размера окна(рис 1.15) Некорректная реакция программы на увеличение размера окна

    Как гласит народная мудрость, за двумя зайцами погонишься – ни одного не поймаешь. Поэтому для начала мы сосредоточимся на первой проблеме – неизменном размере шахматной доски при уменьшении формы. Эта проблема вызвана тем, что по умолчанию Windows Forms не гарантирует вызов события Paint при изменении размеров формы. В принципе, для борьбы с этим недоразумением мы могли бы добавить в обработчик события ResizeСобытие Resize генерируется при изменении размеров формы вызов метода Invalidate, генерирующего событие Paint.

    Однако существует гораздо более элегантное решение: если установить у формы стиль ResizeRedraw, то при изменении размера формы будет автоматически генерироваться событие Paint. Для этого добавьте в обработчик Load строку:

    SetStyle(ControlStyles.ResizeRedraw, true);

    Попробуйте ещe раз запустить программу на выполнение. И что мы видим? Хотя теперь приложение и реагирует на изменение размера окна, появились новая, гораздо более неприятная проблема – при изменении размеров окна форма непрерывно мерцает. Чтобы понять причину этого явления попробуйте изменить цвет формы на зелeный (свойство BackColor ) и снова запустите на выполнение. Мерцания шахматной доски обретут зеленоватый оттенок.

    Итак всe дело в том, что перед вызовом обработчика события Paint класс Form вызывает виртуальный метод OnPaintBackground, который по умолчанию очищает экран цветом BackColor. Эта функциональность позволяет разработчику, использующему GDI+, не заботится об очистке экрана, однако в нашем случае такая "самовольная" очистка формы приводит лишь к мерцанию. Следовательно, нам необходимо каким-нибудь образом запретить форме закрашивать экран перед вызовом обработчика события Paint. Первое, что приходит в голову - перегрузить метод OnPaintBackground protected override void OnPaintBackground(PaintEventArgs pevent)

    {
     // Ничего не делаем 
    }

    Впрочем, если покопаться в документации, можно найти и более изящное решение проблемы. Если установить у формы стиль ControlStyles.Opaque, то форма не будет автоматически закрашивать фон, что собственно нам и нужно: SetStyle(ControlStyles.Opaque, true ); После добавления этой строки в обработчик события Load мерцания наконец-то исчезнут.

    Итак, первую проблему мы решили, но осталась вторая, гораздо более неприятная - некорректное масштабирование шахматной доски при изменении размера окна. На первый взгляд проблема возникает буквально на пустом месте - мы задаeм параметры двойной буферизации и вспомогательных буферов, создаeм новое устройство, после чего перерисовываем экран в обработчике события Paint. Вроде бы ничего противозаконного мы не делаем… Стоп! При создании графического устройства мы задаeм размер вспомогательного буфера, используемого при двойной буферизации, равный размеру клиентской области окна, что вполне логично. Когда мы изменяем размер окна, его клиентская область так же изменяется. А вот размер вспомогательного буфера остаeтся неизменным - мы ведь задаeм его размер только один раз в обработчике события Load. Получается что, при изменении размеров окна происходит рассинхронизация между размером клиентской области окна и вспомогательного буфера, в котором собственно рисуется изображение, после чего приложение естественно начинает работать некорректно.

    Следовательно, нам необходимо добавить в программу коррекцию размера вспомогательного буфера при изменении размера окна. В XNA Framework эта операция выполняется с использованием метода Reset класса GraphicsDevice:

    public void Reset(params PresentationParameters[] 
    presentationParameters);

    где

  • presentationParameters - набор структур PresentationParameters, описывающих новое представление данных на экране. Каждому монитору соответствует своя структура PresentationParameters.
  • Итак, нам придeтся вынести локальную переменную PresentationParameters presentParams за пределы метода Load (то есть сделать еe полем класса MainForm ) и добавить в обработчик события Resize изменение высоты и ширины вспомогательного буфера структуры presentParams с последующим вызовом метода Device.Reset (листинг 1.5).

    private void MainForm_Resize(object sender, EventArgs e)
    {
    // Задаем новые размеры буфера глубины
    presentParams.BackBufferWidth = ClientSize.Width;
    presentParams.BackBufferHeight = ClientSize.Height; 
    // Применяем новые параметры к устройству
    device.Reset(presentParams);
     }

    Примечание

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

    После такой модернизации наша программа наконец-то заработала без ошибок. Хотя так ли это? Как гласит известная пословица, в каждой программе есть как минимум одна ошибка. Наша программа не исключение. Попробуйте из интереса уменьшить еe размер до минимума. Как только высота клиентской области окна станет меньше одного пикселя, в окне Output появятся сообщения от отладочной версии DirectX, а программа аварийно завершится с исключением:

    Direct3D9: (ERROR) :Failed to create driver surface
    Direct3D9: (ERROR) :Reset failed and 
    Reset/TestCooperativeLevel/Release are the only legal
    APIs to be called subsequently
    A first chance exception of type 'Microsoft.DirectX.Direct3D.
    DriverInternalErrorException'
    occurred in Microsoft.DirectX.Direct3D.dll

    Поведение Direct3D вполне логично, ведь попытка вывести изображение на форму с клиентской областью размером менее одного пикселя выглядит, мягко говоря, довольно странной. Однако пользователя такое оправдание вряд ли обрадует, поэтому неплохо бы обезопаситься от подобных казусов, ограничив минимальный размер клиентской области одним пикселем. Это легко можно сделать при помощи свойства MinimumSize, которое задаeт минимальные размеры окна. Правда задание этого свойства во вкладке Properties не самая лучшая идея - область, отводимая формой под клиентскую область, зависит от множества факторов: установленной операционной системы, пользовательских настроек и т.п. Гораздо надeжнее вычислять его прямо в обработчике события Load посредством метода формы SizeFromClientSize, который возвращает размер окна при заданном размере клиентской области:

    // Вычисляем размер окна при клиентской области 1x1 пиксель. 
     Полученное значение присваиваем 
    // свойству MinimumSize 
    MinimumSize = SizeFromClientSize(new Size(1, 1)) ;

    Ещe одной ошибкой стало меньше. Думаю, вы уже убедились, что написать приложение без единой ошибки для такой сложной операционной системы, как Windows,не так уж и просто. Всегда существует вероятность пропустить какой-нибудь нюанс. Например, мы до сих пор не пробовали минимизировать окно при помощи соответствующей стандартной кнопки в правом верхнем углу окна. Попробуйте нажать эту кнопку, и в обработчике события Resize тут же произойдeт исключение - приложение попытается установить размер заднего буфера равным 0 на 0 пикселей. Такое поведение программы обусловлено тем, что при минимизации окна программы Windows уменьшает его размер до нуля пикселей. Следовательно, нам необходимо вставить в обработчик события Resize проверку состояния окна - если окно минимизировано, то программа не должна пытаться изменять размер заднего буфера:

    private void MainFormResize(object sender, EventArgs e) 
    {
    // Если окно не минимизировано, то изменяем размер заднего буфера и сбрасываем устройство 
    if (WindowState != FormWindowState.Minimized) 
       {
           presentParams.BackBufferWidth = ClientSize.Width; 
           presentParams.BackBufferHeight = ClientSize.Height; 
           device.Reset(presentParams); 
        } 
    }

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

    using System;
    using System.Collections.Generic; using System.ComponentModel;
     using System.Data; using System.Drawing; using System.Text;
      using System.Windows. Forms ;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Xna = Microsoft.Xna.Framework;
    using XnaGraphics = Microsoft.Xna.Framework.Graphics;
    namespace GSP.XNA.Book.Ch01.Ex03 
    {
    public partial class MainForm : Form 
    {
    GraphicsDevice device=null; PresentationParameters presentParams ;
    public MainForm()
     {
    InitializeComponent();
     }
    private void MainFormLoad(object sender, EventArgs e) 
    {
    SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
    MinimumSize = SizeFromClientSize(new Size(1, 1));
    presentParams = new PresentationParameters(); 
    presentParams.IsFullScreen = false; presentParams.BackBufferCount = 1; 
    presentParams.SwapEffect = SwapEffect.Discard; 
    presentParams.BackBufferWidth = ClientSize.Width; 
    presentParams.BackBufferHeight = ClientSize.Height;
    device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, 
    DeviceType.Hardware, this.Handle, 
    CreateOptions.SoftwareVertexProcessing | CreateOptions.SingleThreaded,
    presentParams) ; 
    }
    private void MainFormPaint(object sender, PaintEventArgs e) 
    {
    device.Clear(ClearOptions.Target, XnaGraphics.Color.WhiteSmoke, 
    0.0f, 0);
    Xna.Rectangle[] rects = new Xna.Rectangle[32];
    int k = 0;
    for (int j = 0; j < 8; j++)
    for (int i = j % 2; i < 8; i += 2) 
    {
    rects[k] = new Xna.Rectangle(i * ClientSize.Width / 8, Ь  j * 
    ClientSize.Height / 8, ClientSize.Width / 8, ClientSize.Height / 8);
     k++; 
    }
    device.Clear(ClearOptions.Target, XnaGraphics.Color.Brown, 0.0f, 0,
     rects);
    device.Present();
    }
    private void MainForm_Resize(object sender, EventArgs e) 
    {
    if (WindowState != FormWindowState.Minimized) 
    {
    presentParams.BackBufferWidth = ClientSize.Width;
    presentParams.BackBufferHeight = ClientSize.Height;
    device.Reset(presentParams); 
    } 
                     }
    private void MainForm_FormClosed(object sender, FormClosedEventArgs e) 
    {
    if (device != null) 
    {
    device.Dispose(); device = null; 
      } 
    } 
            } 
    }

    Обратите внимание на применение псевдонимов для пространств имен Microsoft.Xna.Framework и Microsoft.Xna.Framework.Graphics , позволившие упростить обращение к структурам Microsoft.Xna.Framework.Color и Microsoft.Xna.Framework.Graphics.Rectangle . Обычная директива using прошлых версий C# в подобных ситуация оказывалась бессильной из-за конфликта с одноименными структурами из пространства имен System.Drawing .

    Практическое упражнение №1.1.

    Создайте приложение, рисующее на экране 10 вложенных разноцветных прямоугольников (рисунок 1.14). Самый крупный прямоугольник должен иметь синий цвет, а самый маленький зелeный. Остальные прямоугольники имеют промежуточные цвета, образуя плавный переход от синего к зелeному. Приложение должно корректно реагировать на изменение размера экрана.

    Примечание

    (рис 1.14) Иллюстрация к практическому упражнению №1.1

    Если у вас возникнут трудности при выполнении этого упражнения, вы можете посмотреть исходный текст готового приложения, которое находится в example.zip в каталоге Ch01\Ex04 .

    Для вычисления промежуточных значений цвета воспользуйтесь конструктором public Color(byte r, byte g, byte b) , создающим структуру Color на основе значений красного, зеленого и синих цветов. Значения цветов находятся в диапазоне 0…255 (0 – минимальная яркость, 255 – максимальная яркость).

    1.2.4. Восстановление работоспособности программы после потери устройства

    Операционная система Windows является многозадачной операционной системой, поэтому параллельно с вашим XNA -приложением могут выполняться десятки других приложений использующих графическую подсистему компьютера. При этом не исключены конфликтные ситуации. Например, какая-нибудь программа может неожиданно изменить разрешение экрана, что вызовет перераспределение видеопамяти, что в свою очередь приведeт к частичной потере информации, хранящейся в видеопамяти. В результате с большой долей вероятности класс GraphicsDevice вашего приложения потеряет информацию в видеопамяти и не сможет продолжать работу. В XNA Framework и DirectX это явление называется потерей устройства ( Device Lost ).

    Для начала нам надо научиться воспроизводить потерю устройства, иначе мы не сможем проверить, как наша программа поведeт себя в случае потери устройства. Я обнаружил, что потеря устройства всегдаЯ не встречал ни одной видеокарты, на которой в этом случае не происходила бы потеря устройства происходит при переключении консольного приложения в полноэкранный режим при помощи клавиш alt + Enter. Следовательно, для форсирования потери устройства вам необходимо:

  • Запустить приложение, использующее DirectX.
  • Запустить консольное приложение. Я предпочитаю пользовать FAR, но если у вас он не установлен, вполне подойдeт и обычная командная строка ( Start | All Programs | Accessories | Command Prompt )
  • Переключиться в полноэкранный режим и обратно в оконный (два раза нажать Alt + Enter ).
  • Если вы проделаете эти операции над примером Ex03 из предыдущего раздел, то он аварийно завершится из-за не перехваченного исключением Microsoft. Xna.Framework.Graphics.DeviceLostException, название которого ясно говорит о причине краха приложения.

    Для определения текущего состояния устройства в классе GraphicsDevice имеется свойство GraphicsDeviceStatus:

    public GraphicsDeviceStatus GraphicsDeviceStatus { get; }

    Свойство возвращает перечислимый тип GraphicsDeviceStatus, который может принимать одно из следующих значений:

  • Normal - устройство функционирует нормально
  • NotReset - устройство потеряно, но может быть восстановлено методом Reset класса GraphicsDevice.
  • Lost - устройство потеряно и пока не может быть восстановлено.
  • Если устройство находится в состоянии NotReset, то его необходимо восстановить, вызвав метод GraphicsDevice.Reset, после чего приложение может продолжать работу, как ни в чeм не бывало. Если же устройство находится в состояние Lost, то тут нечего не поделать - остаeтся лишь выйти из обработчика события Paint и ждать лучших времeн.

    Потеря устройства является очень коварной проблемой, которая может произойти не только до вызова обработчика Paint, но и внутри него. В этом случае XNA Framework в зависимости от ситуации сгенерирует исключения Direct3D.DeviceNotResetException или Direct3D.DeviceLostException. Если сгенерировано исключение Direct3D.DeviceNotResetException, то приложение должно восстановить устройство методом GraphicsDevice.Reset() и снова перерисовать изображение путeм вызова метода Form.Invalidate(), в противном случае просто выйти из обработчика события Paint.

    После всего вышесказанного мы сможем легко научить нашу программу корректно реагировать на потерю устройства. Исходный код обновлeнного обработчика события Paint приведeн в листинге 1.8.

    // Полный текст приложения находится в каталоге Examples\Ch01\Ex05
    private void MainForm_Paint(object sender, PaintEventArgs e)
    {
    try
    { 
    // Если устройство нельзя восстановить, генерируем исключение 
    DeviceLostException (мы его 
    // перехватим в блоке catch)
    Я не встречал ни одной видеокарты, на которой в этом случае не 
    Я происходила бы потеря устройства.
    if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.Lost)
     throw new DeviceLostException();
    // Если устройство можно восстановить, восстанавливаем его
    if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.NotReset) 
    device.Reset();
    // Рисуем шахматную доску
    device.Clear(XnaGraphics.Color.WhiteSmoke) ;
    Xna.Rectangle[] rects = new Xna.Rectangle[32];
    int k = 0;
    for (int j = 0; j < 8; j++)
    for (int i = j % 2; i < 8; i += 2) {
    rects[k] = new Xna.Rectangle(i * ClientSize.Width / 8, Ь  j * 
    ClientSize.Height / 8, ClientSize.Width / 8, ClientSize.Height / 8); 
    k++; 
    }
    device.Clear(ClearOptions.Target, XnaGraphics.Color.Brown, 0.0f, 0,
     rects);
    device.Present();
    } 
    // Если произошло исключение DeviceNotResetException, перерисовываем 
    экран. Восстанавливать 
    // устройство не имеет смысла - наш обработчик события Paint сделает
     это автоматически
    catch (DeviceNotResetException)
    {
    Invalidate();
    } 
    // Если произошло исключение DeviceLostException, то нам остается 
    только ждать до лучших 
    // времен
    catch (DeviceLostException)
    {
    } 
    }

    Практическое упражнение №1.2

    "Обучите" программу, которую вы создали в упражнении №1.1, корректно реагировать на потерю устройства.

    Заключение

    XNA Framework - это высокопроизводительная мультимедийная библиотека для разработки приложений, требовательных к производительности видеоподсистемы и аудиосистемы компьютера, а так же подсистеме ввода-вывода. Одной из причин появления XNA Framework являются некоторые трудности использования Microsoft DirectX на платформе .NET. В отличие от компонентов "обычного" DirectX, сборки Microsoft.Xna.Framework.dll и Microsoft.Xna.Framework.Game.dll соответствуют всем требованиям платформы .NET 2.0 и не привязаны к конкретному языку программирования. В результате эта сборка с одинаковой лeгкостью могут использоваться в любой .NET -совместимой среде программирования вроде Microsoft Visual Basic, Microsoft Visual C# , Microsoft Visual J# или Iron Python. Кроме того, XNA Framework является платформо-независимый библиотекой, что открывает дорогу для переноса приложений на другие платформы, такие как, XBOX 360. Все компоненты XNA Framework условно можно разделить на четыре уровня абстракции: Platform, Core Framework, Extended Framework и Game. В этой лекции мы познакомились с компонентом Graphics (один из компонентов уровня Core Framework ), предназначенным для работы с графической подсистемой компьютера на относительно низком уровне. Плюсом подобного низкоуровневого подхода является высокая производительность приложения, минусом - необходимость учeта разнообразных нюансов вроде коррекции размера вспомогательного буфера при изменении размера окна или и возможности неожиданной потери устройства. Впрочем, даже поверхностные знания основных процессов, творящихся под капотом высокоуровневых графических библиотек, очень полезны при написании надежных высокопроизводительных графических приложений.

    Страницы:

    Примеры к данному курсу Вы можете скачать здесь.

    Первые версии Windows позволяли программисту работать с видеоподсистемой компьютера лишь посредством стандартного интерфейса GDI, предоставляющему программисту унифицированный доступ к различным устройствам вывода графической информации будь то видеокарта, принтер или плоттер. Интерфейс GDI очень прост в использовании - приложение работает с некоторым виртуальным устройством, а GDI самостоятельно транслирует все вызовы приложения в команды конкретной видеокарты или принтера. Преимущества данного подхода очевидны. Например, добавив в приложение всего несколько строк кода, вы можете с легкостью перенаправить вывод с экрана монитора на принтер и получить нужный результат. Кроме того, работа с виртуальным устройством не позволяет некорректно написанному приложению нанести какой-либо ощутимый ущерб стабильности операционной системе и повредить данные других приложений.

    Как всегда, недостатки интерфейса GDI являются продолжением его достоинств. Любому человеку, даже поверхностно знакомому с устройством персонального компьютера, ясно, что, к примеру, видеокарта NVIDIA GeForce 5900 Ultra и лазерный принтер Canon Laser Shot LBP-1120 являются абсолютно разными устройствами. Соответственно, при проектировании универсального интерфейса для работы с данными устройствами неминуемо придeтся жертвовать эффективностью. Это не особо критично для офисных приложений, работающих с достаточно простыми изображениями и не требовательных к скорости обновления экрана. Однако для целого класса приложений (игры, системы виртуальной реальности, пакеты 3D моделирования), критичных к производительности видеоподсистемы компьютера, накладные расходы GDI оказались неприемлемыми. В результате разработчики этих приложений не стремились переносить свои разработки с DOS на Windows, что отнюдь не способствовало росту популярности операционной системы Windows.

    OpenGL

    Так как операционная система без развлекательных приложений вряд ли смогла бы добиться широкой популярности среди домашних пользователей, Microsoft включала сначала в Windows NT 3.5, а затем и в Windows 95 OSR2 поддержку OpenGL - известного кроссплатформенного API для разработки трехмерных графических приложений реального времени. В те времена OpenGL справедливо считался флагманом индустрии трехмерной графики реального времени. Тем не менее, у него все же был ряд недостатков:

  • Так как OpenGL предназначен исключительно для работы с графикой, он решил лишь проблемы низкой производительности графической подсистемы Windows. Работа с аудиоподсистемой компьютера по-прежнему осуществлялась с использованием стандартного медленного интерфейса MCIMedia Control Interface – интерфейс управления мультимедийными устройствами. Содержит набор стандартных команд, позволяющих осуществлять воспроизведение и запись файлов мультимедийных ресурсов. Информацию о MCI можно найти, к примеру, в [К.4] . Аналогичным образом обстояли дела и с получением информации от устройств ввода: клавиатуры, мыши и джойстиков.
  • OpenGL является кросплатформенным API, не привязанным к операционной системе. В результате в нем отсутствуют какие-либо средства для создания окон, загрузки текстур и моделей из файлов и т.д., так как реализация данной функциональности неминуемо бы ограничила переносимость этого API. Подобные особенности несколько усложняют разработку приложений, предназначенных исключительно для платформы Windows.
  • OpenGL является полностью открытым API, не имеющим единого хозяина. Развитие OpenGL координируется наблюдательным комитетом по архитектуре ( ARB ), в который входят ведущие лидеры индустрии, такие как Intel, Microsoft, AMD, NVIDIA, SGI, 3D Labs, Evans Sutherland и т.д. Такое число участников зачастую приводит к конфликтам внутри комитета, последствия которых хорошо описаны в известной басне И.А. Крылова "Лебедь, рак и щука".
  • Кроме того, использование открытого API, пускай и лучшего в индустрии, в качестве одного из краеугольных компонентов, мягко говоря, не отвечает интересам политики Microsoft.

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

    DirectX

    Первым игровым API, разработанным Microsoft, стал WinG. Это был достаточно примитивный API предназначенный для работы исключительно с двухмерной графикой реального времени в операционных системах Windows 3.1 и Windows 95. Видеорежимы с количеством цветов более 256 не поддерживались. Единственным преимуществом WinG была более высокая производительность, чем у GDI. Впрочем, на фоне DOS и OpenGL 1.1 оно выглядело весьма спорным.

    После неудачи с WinG стало ясно, что Microsoft вряд ли сможет в одиночку в короткие сроки разработать конкурентоспособный API для программирования графических приложений реального времени. В результате было принято решение купить британскую компанию RenderMorfics и перенести еe библиотеку Reality Lab на платформу Windows. Так появился Game SDK, позже переименованный в DirectX.

    Из-за сжатых сроков Microsoft не смогла создать на основе Reality Lab полноценный масштабируемый API с заделом на будущее, в результате чего на рынке началась настоящая чехарда версий DirectX, для получения представления о масштабах которой достаточно посмотреть на частоту появления новый версий DirectX (таблица 1.1). Нетрудно заметить, что каждый год выходило не менее одной версии DirectX, причeм новая версия содержала множество кардинальных изменений, в результате чего оказывалась несовместимой с более старой версией. Положение стабилизировалось лишь в 2002-м году с выходом 9-й версии DirectX. С тех пор были выпущены лишь три новых редакции DirectX с незначительными изменениями, после чего Microsoft перешла к выпуску обновлений к DirectX, устраняющих мелкие недочеты, в темпе одно обновление каждые два месяца.

    Примечание

    Любопытно, что графическая подсистема DirectX смогла достичь функциональность OpenGL 1.1 (существовавшего ещe до появления DirectX ) лишь к 7-й версии. Впрочем, к 9-й версии возможности OpenGL и DirectX сравнялись, после чего наметилась тенденция к технологическому отставанию OpenGL от DirectX.

    Даты выхода версий DirectX
    Версия DirectX Дата выхода
    DirectX 1.0 1995
    DirectX 2.0 1996
    DirectX 3.0 / 3.0a 1996
    DirectX 5.0 / 5.1 / 5.2 1997-1998
    DirectX 6.0 / 6.1 1998-1999
    DirectX 7.0 / 7.0a / 7.1 1999
    DirectX 8.0 / 8.1 2000-2001
    DirectX 9.0 / 9.0a / 9.0b / 9.0c + Updates 2002-2006

    Что представляет собой DirectX? Это высокопроизводительная мультимедийная библиотека для программирования приложений требовательных к производительности видеоподсистемы, аудиосистемы и системы ввода-вывода компьютера. В основе DirectX лежит набор COMComponent Object Model – модель компонентных объектов. Определяет стандарты интерфейсов API и бинарные стандарты для связи объектов, не зависящих от языка программирования. Каждый объект COM имеет один или несколько интерфейсов, представляющих собой таблицы функций, связанных с этим объектом. В продаже и свободном доступе имеется множество литературы, посвященной COM, начиная с [К.9] и заканчивая MSDN -интерфейсов, предоставляющих программисту доступ к аппаратному обеспечению компьютера. Эти интерфейсы разработаны Microsoft в тесном сотрудничестве с ведущими производителями аппаратных устройств, таких как Intel, ATI, NVIDIA,Creative и т.д. Поэтому интерфейсы DirectX очень близки к современному аппаратному обеспечению и фактически связаны с аппаратным обеспечением через тонкую прослойку драйверов, минуя стандартные интерфейсы Win32, такие как GDI и MCI.

    В DirectX 9 интерфейсы сгруппированы в три специализированных компонентаВ действительности в составе DirectX имеется ещe четыре компонента: DirectDraw (работа с двухмерной графикой), DirectMusic (проигрывание фоновой музыки), DirectPlay (работа с сетью) и DirectShow (проигрывание видеофайлов). Однако эти компоненты не рекомендуются к использованию, так как они объявлены устаревшими и по видимости будут исключены из будущих версий DirectX (рисунок 1.1):

  • DirectX Graphics, отвечающий за работу с двухмерной и трехмерной графикой.
  • DirectInput, предназначенный для работы с устройствами ввода: мышью, клавиатуры, джойстиками с обратной связью и т.п.
  • DirectSound, используемый для работы со звуковым оборудованием: звуковыми картами, в том числе и с поддержкой трeхмерного звука, MIDI -синтезаторами и т.п.
  • (рис 1.1) Упрощенная схема взаимодействия приложения с устройствами при использовании DirectX

    Managed DirectX

    После выхода платформы .NET Framework встал вопрос об использовании трехмерной графики в приложениях, написанных на управляемых языках вроде C#. Дело в том, что COM -интерфейсы компонентов DirectX проектировались в расчeте на использование в программах на языке C++, в результате чего они активно используют специфические возможности C++, к примеру, указатели. Поэтому, хотя язык C# и позволяет использовать COM -компоненты, применение интерфейсов DirectX в программах на C# сопряжено с рядом проблем: применение же указателей в C# не приветствуется и является плохим тоном программирования на платформе .NET, указатели могут использоваться только в unsafe-блоках, что затрудняет чтение программы и повышает вероятность ошибок. Вдобавок, COM -компоненты не могут использовать преимущества инфраструктуры .NET такие как автоматическая сборка мусора и обработка ошибок с использованием исключений.

    Чтобы облегчить жизнь разработчикам приложений для платформы .NET, Microsoft включила в состав 9-й версии DirectX надстройку над COM -интерфейсами DirectX: Managed DirectX. В октябре 2005 года была анонсирована вторая версия Managed DirectX, которая была фактически переписана с нуля с учeтом новых возможностей платформы .NET 2.0, в частности, Managed DirectX стал активно использовать обобщенные ( Generic ) классы.

    Managed DirectX 2.0 является тонкой надстройкой над DirectX, размещенной в сборке Microsoft.DirectX.dll. Сборка Microsoft.DirectX.dll удовлетворяет всем требованиям платформы .NET 2.0 и не привязана к конкретному языку программирования. Соответственно она может с одинаковой легкостью использоваться в любой .NET -совместимом языке программирования вроде Microsoft Visual Basic, Microsoft Visual C#, Microsoft Visual J#, Iron Python и т.п. Как и оригинальный DirectX, Managed DirectX состоит из трeх компонентов: Direct3D Graphics, DirectInput и DirectSound. Каждому компоненту соответствует одно или несколько пространств имeн (таблица 1.2), содержащих классы и структуры данного компонента. При этом хорошо прослеживается соответствие между классами DirectX и соответствующими COM -интерфейсами неуправляемого DirectX К примеру, COM -интерфейсу IDirect3D9 соответствует брат-близнец класс Microsoft.DirectX.Direct3D.Device, интерфейсу IDirect3DTexture9 – класс Microsoft.DirectX.Direct3D.Texture итак далее. Более того, при должной сноровке при изучении Managed DirectX вполне можно пользоваться документацией по C++ и наоборотЭто очень важный нюанс, так как в настоящее время классический DirectX содержит гораздо более подробную документацию .

    Таблица 1.2. Пространства имeн Managed DirectX 2.0
    Пространство имeн Соответствующий компонент DirectX Описание
    Microsoft.DirectX Используется всеми компонентами DirectX Содержит базовый набор классов, общий для всех компонентов: векторы, матрицы, кватернионы и т.п.
    Microsoft.DirectX.Generic Используется всеми компонентами DirectX В этом пространстве имeн расположены все обобщенные классы
    Microsoft.DirectX.Direct3D DirectX Graphics Отвечает за работу с 3D графикой
    Microsoft.DirectX.Direct3D.CustomVertex DirectX Graphics Содержит структуры типовых форматов вершин
    Microsoft.DirectX.DirectInput DirectInput Работа с устройствами ввода
    Microsoft.DirectX.XInput DirectInput Работа с устройствами ввода игровой приставки XBOX
    Microsoft.DirectX.DirectSound DirectSound Работа со звуком, в том числе и трeхмерным

    Однако Beta версия Managed DirectX 2.0 не была доведена до Release, став частью значительно более амбициозного проекта XNA Framework.

    XNA Framework

    В 2005-м году в продажу поступила игровая приставка Microsoft следующего поколения – XBOX 360. На этот раз Microsoft решила отказаться от использования процессоров привычной архитектуры x86 в пользу процессора PowerPC, не совместимого с x86. Сама архитектура игровой приставки так же значительно отличалась от персонального компьютера, работающего под управлением операционной системы Windows. Таким образом, впервые в истории у Microsoft оказалось две несовместимых игровых платформы: Windows и XBOX 360. Это обстоятельство ощутимо осложнило жизнь разработчикам игр, так как написание приложения с поддержкой обоих платформ фактически сводится к написанию отдельных приложений для каждой платформы, что под силу лишь достаточно крупным конторам.

    Подобная изоляция двух платформ не устраивала Microsoft, поэтому возникла необходимость создания инструментария позволяющего небольшим конторам и начинающим разработчикам создавать кроссплатформенные приложения, работающие как на платформе Windows, так и на XBOX 360. В качестве основы было решено использовать платформу .NET: как известно, .NET -приложения компилируются в промежуточный язык IL, а финальная компиляция в машинный код происходит только при запуске приложения на конкретной системе. Таким образом, .NET -приложению, в общем-то, безразлично, какой процессор в данный момент установлен в системе.

    Но на практике все оказалось несколько сложнее:

  • .NET Framework содержит мощную библиотеку классов на все случаи жизни, включая разработку приложений баз данных, web -сервисов, web -сайтов. Разумеется, данная функциональность является, мягко говоря, несколько избыточной для игровой приставки. Кроме того, ряд классов .NET Framework сильно привязаны к операционной системе Windows (пространства имен System.Windows.Forms, System.Drawings ), а перенос Windows на игровую приставку является весьма сомнительной затеей.
  • Managed DirectX, является достаточно тонкой настройкой над DirectX. А так как DirectX – это один из компонентов платформы Windows, Managed DirectX автоматически оказывается непереносимым API, привязанным к платформе Windows. Кроме того, классы и структуры Managed DirectX активно используют функциональность из пространств имен System.Windows.Forms и System.Drawing.
  • Первая проблема была решена путeм реализации на XBOX лишь подмножества классов .NET Framework, известного как .NET Compact Framework. Для решения второй проблемы был разработан XNA Framework -высокопроизводительный кроссплатформенный API для разработки графический приложений для платформ Windows и XBOX 360.

    Условно все компоненты XNA Framework можно разделить на 4 уровня абстракции (рисунок 1.2):

  • Platform (Платформа) - самый нижний уровень, содержащий платформо-зависимые API, такие как неуправляемый DirectX. В подавляющем большинстве случаев приложение может ничего не знать о существовании этого уровня, используя компоненты более высоких уровней абстракции. Более того, прямое обращение уровню Platform неминуемо сузит диапазон платформ, поддерживаемых приложением: не исключено, что Microsoft в будущем добавит поддержку XNA Framework и в операционные системы для карманных устройств.
  • Core Framework (Основной Каркас) - нижний платформо-независимый уровень XNA, обеспечивающий базовую функциональность. Размещается в сборке Microsoft.Xna.Framework.dll и содержит 5 компонентов: Graphics (работа с графикой), Audio (работа со звуком), Input (работа с устройствами ввода-вывода), Math (математические расчеты), Storage (работа с файловой системой). Классы и структуры каждого компонента сгруппированы в пространства имен (таблица 1.3). На платформе Windows первые три компонента ( Graphics, Audio, Input ) являются надстройками над DirectX, а компонент Storage - надстройкой над классами .NET Framework для работы с файловой системой. Однако следует всегда помнить о том, что на других платформах всe может обстоять совершенно иначе.
  • Extended Framework (расширенный каркас) - набор высокоуровневых классов, решающих типовые задачи, встающие перед разработчиком игр: инициализация графического устройства, организация цикла обработки сообщений, экспорт моделей и текстур из графических редакторов. По сути Extended Framework можно считать универсальным игровым движком ( Game Engine ) начального уровня. Размещается в сборке Microsoft.Xna.Framework.Game.dll.
  • Game - собственно приложение пользователя, то есть наши с вами программы. К слову, в комплект XNA входит несколько простых игр ( Starter Kits ), которые можно использовать в качестве заготовок для своих приложений.
  • (рис 1.2) Уровни XNA Framework
    Таблица 1.3. Пространства имен сборки Microsoft.Xna.Framework.dll
    Пространство имен Соответствующий компонент XNA Назначение
    Microsoft.Xna.Framework Math Математические расчеты: матричная алгебра, аналитическая геометрия, проверка столкновений и т.д. В Managed DirectX эта функциональность (в урезанном виде) реализовывалась посредством библиотеки D3DX, являющейся частью DirectX. XNA Framework выполняет математические расчеты собственными средствами, что в некоторых случаях несколько повышает производительность благодаря отсутствию накладных расходов взаимодействия с COM.
    Microsoft.Xna.Framework.Graphics Работа с графикой
    Microsoft.Xna.Framework.Graphics.PackedVector Graphics Работа с упакованными векторами. Примером упакованного вектора является 32-х битное число, содержащее информацию о яркости красной, синей и зеленой компонентах цвета
    Microsoft.Xna.Framework.Audio Audio Работа со звуком
    Microsoft.Xna.Framework.Input Input Работа с устройствами ввода (клавиатура, мышь, джойстики).
    Microsoft.Xna.Framework.Storage Storage Работа с файловой системой текущей платформы: загрузка и сохранение настроек приложения, "сохраненных игр" ( Save Games ) и т.д.

    Ничего страшного, если у вас на первых порах будет рябить в глазах от обилия компонентов. По мере изучения XNA Framework всe встанет на свои места. В первой лекции мы познакомимся с некоторыми классами пространства имен Microsoft.Xna.Framework.Graphics, и научимся использовать их для визуализации относительно простых двухмерных изображений.

    1.1. Создание простейшего приложения, использующего XNA Framework.

    (рис 1.3) Наше первое приложение(Ex01), использующее XNA. Graphic User Interface – графический пользовательский интерфейс

    Как известно, лучший способ получить представление о новой технологии – написать с еe помощью простейшее приложение. Так мы и поступим. Наше первое приложение, использующее XNA Framework, будет просто закрашивать форму синим цветом (рисунок 1.3). Для создания GUIGraphic User Interface – графический пользовательский интерфейс -интерфейса мы воспользуемся библиотекой Windows Forms, являющуюся стандартом для платформы .NET.

    Для начала запустите Microsoft Visual Studio 2005 и создайте новый проект GUI –приложения для платформы Windows (File | New | Project...). В раскрывшемся окне выберите Visual C# | Windows | Windows Application, введите название приложения, снимите флажок Create directory for Solution и нажмите Ok. Переименуйте файл формы из Form1.cs в MainForm.cs.

    Следующий шаг – подключение сборки Microsoft.Xna.Framework.dll, содержащий компоненты слоя Core Framework, включая необходимый нам компонент Graphics.Для подключения сборки щeлкните правой кнопкой мыши на узле Reference в окне Solution Explorer и выберите в контекстном меню пункт Add Reference... (рисунок 1.4). В открывшемся окне выберете сборку Microsoft.Xna.Framework и нажмите кнопку Ok (рисунок 1.5).

    (рис 1.5) Вкладка Solution Explorer(рис 1.4) Окно Add Reference

    Теперь мы можем приступать к собственно написанию программы. Откройте окно редактирования исходного кода, щелкнув правой кнопкой мыши по форме и выбрав пункт View Code контекстного меню (либо нажав на кнопке View Code в верхней части окна Solution Explorer ). Так как мы будем активно использовать классы из пространства имен Microsoft.Xna.Framework.Graphics, было бы логично добавить в начало программы следующую строку:

    using Microsoft.Xna.Framework.Graphics;

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

    public GraphicsDevice(GraphicsAdapter adapter, DeviceType deviceType, 
    IntPtr renderWindowHandle, CreateOptions creationOptions, params PresentationParameters[] 
    presentationParameters);

    где

  • adapter - экземпляр класса GraphicsAdapter, соответствующей используемой видеокарте (многие современные компьютеры содержат две и более видеокарты). Для указания видеокарты по умолчанию достаточно передать в качестве данного параметра значение статического свойства GraphicsAdapter.DefaultAdapter.
  • deviceType - тип устройства, задаваемый с использованием перечислимого типа DeviceType (таблица 1.4). На практике обычно используется значение DeviceType .Hardware.
  • renderWindowHandle - дескриптор окна или элемента управления, который будет использоваться для вывода информации.
  • creationOptions - набор битовых флагов перечислимого типа CreateOptions, задающих режим работы устройства (таблица 1.5). В нашем случае мы будем использовать режимы CreateOptions.SoftwareVertexProcessing и CreateOptions.SingleThreaded.
  • PresentationParameters - набор структур PresentationParameters, описывающих представление данных на экране монитора. Каждому монитору соответствует своя структура PresentationParameters. Так как наши приложение будет осуществлять вывод только на один монитор, мы ограничимся одной структурой PresentationParameters. В таблице 1.6 приведено описание некоторых свойств структуры PresentationParameters.
  • Если попытка создания устройства заканчивается неудачей, конструктор генерирует исключение. Подобная ситуация возникает, к примеру, при попытке создания устройства с использованием флага CreateOptions.HardwareVertexProcessing на видеокарте, не имеющей аппаратных вершинных процессоров. Примерами таких видеокарт являются Intel GMA 900 и Intel GMA950, интегрированные в чипсеты i915G и i945G соответственно. Так как флаг HardwareVertexProcessing влияют исключительно на выполнение вершинных шейдеров, в то время как примеры этой лекции не используют эту функциональность, применение данного флага некоим образом не повлияет на производительность наших приложений и лишь неоправданно увеличит требования к видеокарте. Поэтому в примерах первой лекции курса мы ограничимся использованием лишь флага SoftwareVertexProcessing.

    Таблица 1.4. Значения перечислимого типа DeviceType
    Значение Описание
    Hardware Аппаратное устройство
    Reference Устройство эмулируется средствами DirectX SDK. Обычно используется для эмуляции функциональности, не поддерживаемой текущей видеокартой (например, эмуляция пиксельных шейдеровОсновы вершинные и пиксельных шейдеров будут рассмотрены в лекции 2.3 на GeForce2). Правда, такую эмуляцию реально использовать лишь в отладочных целях, так как производительность приложений в этом режиме редко превышает один кадр в секунду.
    NullReference Нуль-устройство, игнорирующее все поступающие команды (наподобие устройства NUL в MS-DOS и Windows ). Может использоваться для оценки производительности приложения на бесконечно быстрой видеокарте.
    Значения перечислимого типа CreateFlags
    Значение Описание
    HardwareVertexProcessing Аппаратная обработка вершин средствами GPUGraphic Processor Unit – графический процессор, установленный на видеокарте
    SoftwareVertexProcessing Программная обработка вершин средствами CPU
    MixedVertexProcessing Смешанная обработка вершин. Позволяет приложению самостоятельно переключаться между программной и аппаратной обработкой вершин. Полезна, к примеру, при использовании вершинных шейдеров версии 2.0 на GeForce3, который аппаратно поддерживает только вершинные шейдеры версии 1.1. В этом случае вершинные шейдеры версии 1.1 можно выполнять аппаратно, а версии 2.0 – в режиме программной эмуляции
    NoWindowChanges Устройство не будет автоматически восстанавливаться после потери фокуса окном и т.д. Все эти действия ложатся на плечи программиста. Данное значение применяется очень редко
    SingleThreaded Увеличивает производительность за счет отключения критических секций, позволяющих одновременно обращаться к устройству из нескольких потоков. Рекомендуется всегда по возможности использовать этот флаг.
    SinglePrecision Переключает математический сопроцессор в режим пониженной точности. Все вычисления с плавающей точкой, включая использующие тип double, будут выполнять с точностью 7 знаков. Более подробная информация об этом режиме приведена во врезке
    Некоторые свойства структуры PresentationParameters
    Поле Описание
    bool IsFullScreen При выводе на поверхность компонента или формы этому свойству необходимо присвоить значение false. Если же приложение является полноэкранным, то используется значение true
    int BackBufferCount Задает количество вспомогательных буферов, используемых для борьбы с эффектом мерцания при смене кадров. При использовании одного вторичного буфера изображение сначала рисуется во вспомогательном буфере, после чего уже готовое изображение копируется в экранный буфер. Этот процесс называется двойной буферизацией. Буферизация, использующая два вспомогательных буфера, называется тройной. Более подробно различные типы буферизации будут рассмотрены в соответствующих разделах курса. А пока мы будем использовать двойную буферизацию, присваивая полю BackBufferCount значение 1.
    int BackBufferWidth Ширина вспомогательных буферов в пикселях. Если этот параметр равен 0, то конструктор метода Device рассчитывает его автоматически, полагая равным ширине клиентской области окнаЧасть окна, используемая приложением для вывода информации. В клиентскую область окна не входят заголовок окна, рамки по краям окна и т.д
    int BackBufferHeight Высота вспомогательных буферов в пикселях. Если этот параметр равен 0, то конструктор метода Device рассчитывает его автоматически, полагая равным высоте клиентской области окна
    Direct3D.SwapEffect SwapEffect Указывает, каким образом осуществляется переключение буферов при двойной и тройной буферизации. Подавляющее большинство программ присваивают этому полю значение SwapEffect.Discard – в этом случае видеокарта сама выбирает режим переключения буфера, наиболее подходящий в конкретной ситуации.
    Точность вычислений с плавающей точкой процессоров архитектуры x86

    Блок вычислений с плавающей точкой ( FPU ) процессоров x86До появления i486DX этот блок располагался в отдельной микросхеме, называемой математическим сопроцессором (8087, 80287, 80387) содержит восемь 80-ти битных регистров общего назначения, используемых для хранения операндов и результатов вычислений. Иными словами, независимо от используемых типов данных сопроцессор всегда оперирует с 80-ти битным форматом с плавающей точкой, известным как extended double. Если операнды имеют меньшую разрядность (например, используется тип float ), то они автоматически конвертируются в 80-ти битный формат, а результат перед копированием в память переводится обратно в 32-х битный формат float. Так как подобная точность вычислений оказывается излишней, в одном из управляющих регистров процессора ( CW ) имеются два бита ( PC ), управляющие точностью вычислений. В зависимости от значения битов, задающихся при запуске приложения, вычисления выдадутся с 7-ю, 16-ю или 19-ю значащими цифрами. Ещe раз хочу подчеркнуть важную деталь. Формат чисел в регистрах всегда остаeтся 80-ти битным, просто с понижением точности младшие разряды числа с плавающей точкой могут содержать недостоверную информацию.

    Два бита PC регистра CW устанавливаются при запуске потока ( thread ) и, как правило, не меняются в процессе выполнения приложения. В то же время, параметр CreateOptions.SinglePrecision приказывает конструктору класса GraphicsDevice изменить служебный регистр CW таким образом, чтобы установить внутри сопроцессора точность вычислений 7 знаков. В результате, после вызова данного метода вычисления, включая использующие типы double, будут выполняться с 7-ю значащими знаками. Правда существуют и исключения: так, к примеру, биты PC регистра CW оказывают влияние на точность сложения, вычитания, умножения, деления и вычисления квадратного корня, но совершенно не затрагивают точность команд вычисления синуса и косинуса.

    В общем, здесь очень много различных тонкостей, но разбираться в них, в общем-то, не нужно. Достаточно запомнить, что флаг CreateOptions.SinglePrecision таит много подводных камней, поэтому применять его стоит только при реальной необходимости поднять производительно математической подсистемы любой ценой. В остальных случаях вы лишь повысите вероятность возникновения различных трудно обнаруживаемых ошибок. Ведь никто не сможет гарантировать, что одна из используемых вами библиотек не начнeт вести себя как-то странно, столкнувшись с аномально низкой точностью вычислений, использующих типы double. Например, пониженная точность может очень негативно сказаться на качестве генератора псевдослучайных чисел.

    Итак, для создания графического устройства мы должны объявить в тексте класса формы поле класса

    GraphicsDevice:
    GraphicsDevice device=null;

    Затем в обработчик события Load формы необходимо вставить код создания нового экземпляра класса GraphicsDevice c заданными параметрами листинг 1.1).

    private void MainForm_Load(object sender, EventArgs e)
    {
    // Инициализируем все поля структуры presentParams значениями по умолчанию
    PresentationParameters presentParams = new PresentationParameters(); 
    // Мы будем осуществлять вывод на поверхность формы, то есть в оконном режиме
    presentParams.IsFullScreen = false; 
    // Включаем двойную буферизацию
    presentParams.BackBufferCount = 1; 
    // Переключение буферов должно осуществляться с максимальной эффективностью
    presentParams.SwapEffect = SwapEffect.Discard; 
    // Задаем ширину и высоту клиентской области окна. Если присвоить этим полям значение 0 (что 
    // и происходит по умолчанию), то конструктор класса GraphivsDevice автоматически рассчитает 
    // значение этих полей и занесeт их в структуру presentParams. Поэтому эти две строки, в 
    // принципе, можно и опустить.
    presentParams.BackBufferWidth = ClientSize.Width;
    presentParams.BackBufferHeight = ClientSize.Height;
    // Создаем новое устройство, обладающее следующими характеристиками:
    // - Устройство будет использовать видеоадаптер по умолчанию
    // - Устройство будет аппаратным
    // - Вывод будет осуществляться на поверхность текущей формы
    // – Обработка вершин будет осуществляться средствами GPU
    // - Представление данных на экране задаeтся структурой 
    presentParams (см. выше)
    // – Доступ к устройству возможен только из одного потока приложения
    device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, 
    DeviceType.Hardware, Handle, CreateOptions.SoftwareVertexProcessing 
    | CreateOptions.SingleThreaded, presentParams); 
    }

    Создав объект устройства, можно приступать к реализации закраски формы синим цветом. Для этого воспользуемся методом Clear класса GraphicsDevice, который очищает форму путeм закраски еe заданным цветом:

    public void Clear(ClearOptions options, Color color, float depth, 
    int stencil);

    где

  • options - набор битовых флагов, указывающих какие буферы необходимо очистить. Для очистки экранного буфера используется флаг ClearOptions.Target. Остальные флаги ClearOptions.DepthBuffer и ClearOptions.Stencil, используемые для очистки соответственно буфера глубины и буфера шаблона, которые будут рассмотрены в следующих лекциях.
  • color - цвет, которым будет закрашен экран. Задаeтся с использованием структуры Microsoft.Xna.Framework.Graphics.Color, являющейся функциональным аналогом структуры System.Drawing.Color. Появление такого брата-близнеца обусловлено необходимостью сделать XNA Framework независимым от функциональности платформы Windows.
  • depth - значение, которым будет "закрашен" буфер глубины.
  • stencil - значение, которым будет заполнен буфер шаблона.
  • Вызов метода Clear необходимо вставить в обработчик события Paint, вызываемый каждый раз при необходимости перерисовки содержимого формы:

    private void MainForm Paint(object sender, PaintEventArgs e) 
    {
    device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
    Graphics.Color.CornflowerBlue, 
    0.0f, 0) ; 
    }

    Обратите внимание на использование полного имени структуры Microsoft.Xna.Framework.Graphics.Color с указанием пространства имен. Если это не сделать, возникнет конфликт с одноименной структурой из пространства имен System.Drawing.

    Класс GraphicsDevice имеет и более простую перегрузку ( override ) метода, предназначенную для очистки исключительно экранного буфера:

    public void Clear(Color color);

    где

    color - цвет, которым заполняется весь экран

    Использование данного варианта перегрузки метода позволяет несколько упростить код приложения:

    private void MainFormPaint(object sender, PaintEventArgs e)
    {
    device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue); 
    }

    По окончанию работы приложение должно удалить графическое устройство при помощи метода Dispose. Для этой цели идеально подходит обработчик события FormClosed (листинг 1.2).

    private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
    {
    //  Если устройство существует
    if (device != null)
    { //  Удаляем (освобождаем) устройство
    device.Dispose(); 
    // На всякий случай присваиваем ссылке на устройство значение 
    null device = null;
    }
    }

    Если вы забудете удалить объект устройства, .NET самостоятельно попытается вызвать метод Dispose экземпляра класса GraphicsDevice в процессе сборки мусора (если быть более точным, сборщик мусора вызывает метод Finalize, который довольно часто реализует вызов метода Dispose ). Но здесь имеется один нюанс. Как известно, сборщик мусора для вызова методов Finalize удаляемых объектов создаeт отдельный поток, в то время как на платформе Windows устройство Direct3D имеет право удалить только поток, создавший это устройство. Соответственно, деструктор объекта GraphicsDevice, вызываемый из параллельного потока, не сможет корректно удалить устройство Direct3D.

    Примечание

    Даже если вы не укажете при создании устройства флаг CreateOptions.SingleThreaded, сборщик мусора всe равно не сможет корректно удалить объект.

    Вроде бы всe. Давайте попробуем запустить полученное приложение на выполнение (клавиша F5 ). Не смотря на то, что метод Clear вызывается при каждой перерисовке окна (в этом легко убедится, установив точку останова на строку с вызовом этого метода при помощи клавиши F9 ) , на внешнем виде формы это никак не отражается. Интересно, с чем это может связано?

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

    void Present()

    Добавьте вызов этого метода в конец обработчика события Paint и снова запустите программу на выполнение - на экране появится окно, закрашенное синим цветом, что и требовалось. Исходный код готового приложения находится в каталоге Ch01\Ex01.

    1.2. Визуализация шахматной доски

    Одна из перегрузок метода GraphicsDevice.Clear позволяет очищать не весь экран целиком, а лишь заданную прямоугольную область формы:

    public void Clear(ClearOptions options, Color color, 
    float depth, int stencil, Rectangle[] rect);

    где

    rect - массив структур Microsoft.Xna.Framework.Rectangle, задающих прямоугольные области экрана, которые должны быть очищены. Области экрана задаются в оконных координатах формы - начало координат расположено в левом верхнем углу. Структура Microsoft.Xna.Framework.Rectangle является близнецом одноименной структуры из пространства имен System.Drawing, и используется во избежание привязки XNA Framework к платформе Windows.

    Примечание

    Структура Rectangle объявлена в пространстве имен Microsoft.Xna.Framework, так как он используется многими классами XNA Framework, в том числе и не из пространства имен Microsoft.Xna.Framework.Graphics.

    К примеру, следующий фрагмент кода нарисует в центре экрана зелeный прямоугольник на синем фоне (рисунок 1.6):

    // Закрашиваем экран синим цветом
    device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
    // Создаeм массив с координатами областей экрана, которые необходимо закрасить. Нам
    // нужна всего одна область
    Microsoft.Xna.Framework.Rectangle[] rect = new Microsoft.Xna.
    Framework.Rectangle[1];
    // Задаeм координаты области экрана, расположенной в центре экрана и занимающей 25%
    // площади экрана
    rect[0] = new Microsoft.Xna.Framework.Rectangle(ClientSize.Width/4,
    ClientSize.Height/4, ClientSize.Width/2, ClientSize.Height/2);
    // Закрашиваем эту область зелeным цветом
    device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
    Graphics.Color.Green, 0.0f,
    4> 0, rect);
    (рис 1.6) Зелeный квадрат на синем фоне, нарисованный с использованием метода Clear

    В принципе при грамотном использовании только одного метода Clear можно получать довольно интересные изображения. К примеру, никто не мешает нам нарисовать шахматную доску (рисунок 1.7). Для этого необходимо очистить экран белым цветом, затем создать массив областей экрана, соответствующих клеткам доски коричневого цвета и ещe раз очистить экран, но уже коричневым цветом (листинг 1.4).

    (рис 1.4) Шахматная доска, нарисованная с использованием метода Clear(рис 1.7) // Полный код приложения находится в каталоге Examples\Ch01\Ex02
    private void MainFormPaint(object sender, PaintEventArgs e)
    {
    // Очищаем экран белым цветом
    device.Clear(Microsoft.Xna.Framework.Graphics.Color. WhiteSmoke);
     // Создаeм массив областей закраски, соответствующих коричневым клеткам
    Microsoft.Xna.Framework.Rectangle[] rects = 
    new Microsoft.Xna.Framework.Rectangle[32] ;
    int k = 0; 
    // Перебираем коричневые клетки шахматной доски for 
    (int j = 0; j < 8; j++)
    for (int i = j % 2; i < 8; i += 2)
     {
    // Заносим в массив координаты очередной клетки
    rects[k] = new Microsoft.Xna.Framework.Rectangle(i * 
    ClientSize.Width / 8, 
    4 j * ClientSize.Height / 8, ClientSize.Width / 8, 
    ClientSize.Height / 8);
    k++;
    }
    // Закрашиваем все области из массива rects коричневым цветом
    device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
    Graphics.Color.Brown, 0.0f, 0, 
    4> rects);
    device.Present();
     }

    1.2.1. Конфигурирование DirectX для отладки приложени

    Как вы помните, на платформе Windows XNA Framework в некотором роде является высокоуровневой надстройкой над DirectX. Соответственно, на платформе Windows подавляющее большинство вызовов методов XNA Framework так или иначе транслируется в вызовы методов DirectX. В большинстве случаев это обстоятельство можно полностью игнорировать. Тем не менее, при возникновении различных "аномалий" в приложении обращение к нижележащему уровню может помочь быстро решить проблему.

    Конфигурирование DirectX осуществляется при помощи утилиты DirectX, запускаемой командой Start | All Programs | Microsoft DirectX SDK | DirectX Utilities | DirectX Control Panel. Внешний вид этой утилиты изображeн на рисунке 1.8. Как видно, данная утилита представляет собой обычное диалоговое окно с набором вкладок, отвечающих за настройку различных компонентов DirectX. Рассмотрим наиболее важные из них.

    (рис 1.8) Внешний вид утилиты конфигурирования DirectX. Открыта вкладка Direct3D

    Вкладка Direct3D предназначена для настройки компонента Direct3D Graphics. Как правило, эта вкладка используется для переключения между отладочной и "обычной" версией Direct3D при помощи переключателей Use Debug Version of Direct3D и Use Retail Version of Direct3D соответственно (расположены в группе Debug/Retail D3D Runtime ). Отладочная версия Direct3D Graphics проводит дополнительную проверку правильности параметров передаваемых классам Direct3D Graphics и правильности выполнения этих методов. Информация о различных подозрительных ситуациях и ошибках передаeтся в отладчик (например, в отладчик Visual Studio 2005 ).

    При разработке и отладке приложений рекомендуется всегда использовать отладочную версию Direct3D Graphics. Так же полезно установить ползунок Debug Output Level, отвечающий за подробность отладочной информации в крайнее правое положение, чтобы получать информацию о любых подозрительных ситуациях. Ведь согласно "эффекту бабочки", даже самый безобидный на первый взгляд недочeт может привести к каскаду трудноуловимых ошибок.

    В группе Debugging желательно включить следующие флажки: Maximum Validation (максимальная проверка корректности параметров, передаваемых классам Direct3D Graphics ), Enable Shader Debugging (отладка шейдеров) и Break on Memory Leaks (обнаружение утечек памяти).

    Внимание

    Отладочная ( Debug ) версия DirectX Graphics значительно медленнее обычной ( Retail ) версии. Поэтому не забывайте отключать отладочную версию DirectX по завершению отладки. В противном случае вы рискуете столкнуться с аномально низкой производительностью трeхмерных игр и аналогичных приложений. Для того чтобы переключиться в нормальный режим, достаточно просто включить радиокнопку Use Retail Version of DirectX – остальные опции вроде Debug Output Level не оказывают никакого влияния на обычную версию Direct3D Graphics.

    Debug View

    По умолчанию Visual Studio 2005 Pro не отображает сообщения от отладочной версии DirectX, а в бесплатной версии Visual C# 2005 Express подобная функциональность не предусмотрена в принципе. Поэтому я включил в example.zip бесплатную программу Марка Руссиновича Debug View, расположенную в каталоге \Tools\DebugView. Скопируйте еe на локальный жесткий диск компьютера и запустите файл DebugView.exe. На экране появится окно следующего вида (рисунок 1.9). Наибольший интерес для нас представляет центральная часть окна, в которой отображают отладочные сообщения от всех приложений, выполняющихся в данный момент на компьютере. Если вы поработаете некоторое время на компьютере при запущенной утилите Debug View, то наверняка заметите множество отладочных сообщений от разнообразных приложений.

    (рис 1.9) Приложение Debug View

    Откройте панель управления DirectX, и включите отладочную версию DirectX. Запустите на выполнение приложение, рисующую шахматную доску (пример Ch01\Ex02 ), поработайте с ним некоторое время, после чего завершите. Тем временем в окне Debug Info появится информация следующего вида:

    // Библиотека Direct3D загружается в адресное пространство 
    нашего приложения Direct3D9: :====> ENTER: DLLMAIN(041dd6e0): 
    Process Attach: 0000041c, tid=000016a8 Direct3D9: :====> EXIT: 
    DLLMAIN(041dd6e0): Process Attach: 0000041c
    // Direct3D находится в отладочном режиме
    Direct3D9: (INFO) :Direct3D9 Debug Runtime selected.
    // Расширенные возможности отладки Direct3D недоступны 
    (эта функциональность доступна
    // только для DirectX-приложений, написанных на C++)
    
    D3D9 Helper: Enhanced D3DDebugging disabled; Application was 
    not compiled with D3D_DEBUG_INFO
    // Сообщение с пометкой INFO содержат разнообразную служебную 
    информацию о ходе
    // выполнения приложения. В частности следующее сообщение означает, 
    что устройство
    // находится в режиме Software Vertex Processing (Программная обработка
     вершин). Иными
    // словами, при создании графического устройства был использован флаг
    // CreateOptions.SoftwareVertexProcessing.
    Direct3D9: (INFO) :======================= Hal SWVP device selected
    Direct3D9: (INFO) :HalDevice Driver Style 9
    Direct3D9: :DoneExclusiveMode
    Direct3D9: :====> ENTER: DLLMAIN(041dd6e0): Process Detach 0000041c,
     tid=0000022c
    // Освобождение ресурсов Direct3D завершено Direct3D9: (INFO) :MemFini!
    // Завершение работы Direct3D
    Direct3D9: :====> EXIT: DLLMAIN(041dd6e0): Process Detach 0000041c

    Обратите внимание

    В столбце Time указано время поступления отладочного сообщения, что облегчает идентификацию сообщений. По умолчанию используется относительное время – за точку отсчeта берется время поступления первого события, т.е. время наступление первого события всегда равно 0.0 секунд.

    Как видно, приложение выполняется без каких-либо эксцессов. Теперь закомментируйте строку device.Dispose() в обработчике события Close() и снова запустите приложение на выполнение. На этот раз отладочные сообщения будут несколько отличаться:

    Direct3D9: :====> ENTER: DLLMAIN(042dd6e0): Process Attach: 00000298,
     tid=00000910
    Direct3D9: :====> EXIT: DLLMAIN(042dd6e0): Process Attach: 00000298
    Direct3D9: (INFO) :Direct3D9 Debug Runtime selected.
    D3D9 Helper: Enhanced D3DDebugging disabled; Application was not
     compiled with D3D_DEBUG_INFO
    Direct3D9: (INFO) :======================= Hal SWVP device selected
    Direct3D9: (INFO) :HalDevice Driver Style 9 Direct3D9: 
    :DoneExclusiveMode
    // Предупреждение. К устройству, не рассчитанному на работу в
     многопоточном режиме (в
    // конструкторе класса GraphicsDevice указан флаг 
    CreateOptions.SingleThreaded)
    // пытается обратиться другой поток.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    // Ошибка! Устройство может быть уничтожено только потоком, 
    создавшим его
    Direct3D9: (ERROR) :Final Release for a device can only 
    be called from the thread that the device was created from.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    Direct3D9: (WARN) :Device that was created without 
    D3DCREATE_MULTITHREADED is being used by a thread 
    other than the creation thread.
    Direct3D9: (WARN) :Device that was created without D
    3DCREATE_MULTITHREADED is being used by a thread other 
    than the creation thread.
    Direct3D9: :====> ENTER: DLLMAIN(042dd6e0): Process 
    Detach 00000298, tid=00000520
    Direct3D9: (INFO) :MemFini!
    Direct3D9: :====> EXIT: DLLMAIN(042dd6e0): Process
     Detach 00000298

    Хотя приложение и работает нормально, по отладочным сообщениям можно легко догадаться, что при завершении работы приложения сборщик мусора попытался уничтожить устройство, что естественно не удалось – ведь сборщик мусора вызывает методы Finalize в отдельном потоке, в то время как устройство Direct3D может удалить лишь тот поток, который его создал. Таким образом, отладочная версия DirectX помогла нам легко локализовать проблему.

    Единственное неудобство доставляют отличия между Direct3D и XNA Framework. К примеру, устройство Direct3D по умолчанию запускается в однопоточном режиме, а включение поддержки многопоточного режима осуществляется путем указания флага D3DCREATE_MULTITHREADED. А вот класс GraphicsDevice, напротив, по умолчанию создаeт графическое устройство с поддержкой многопоточности, а отключение данной функциональности осуществляется путем указания флага CreateOptions.SingleThreaded. Кроме того, отладочная версия Direct3D нечего не знает о .NET Framework – вместо того, чтобы сообщить о проблемах из-за удаления объекта GraphicsDevice сборщиком мусора она просто жалуется на странное поведение непонятно откуда взявшегося дополнительного потока. Впрочем, получив некоторый опыт чтения сообщений отладочной версии Direct3D, вы перестанете обращать внимание на подобные нюансы.

    Взаимодействие XNA Framework c DirectX

    Как говорилось выше, XNA Framework по сути является прослойкой между .NET и DirectX. Но насколько эта тонка прослойка и оказывает ли она существенное влияние на производительность приложения? Чтобы ответить на этот вопрос мы рассмотрим работу XNA Framework на примере метода

    Direct3D.Device.Present, декомпилировав его с использованием .NET Reflector, который находится в example.zip в каталоге Tools\NET Reflector:

    public unsafe void Present()
    {
    // Проверяет, не был ли вызван метод Dispose для данного 
    экземпляра класса
    // GraphicsDevice. Если метод Dispose уже был вызван, 
    генерируется исключение
    // ObjectDisposedException
    InternalHelper.CheckDisposed(this, (void*) this.pComPtr); 
    // Получает указатель на COM–интерфейс IDirect3DDevice9, 
    являющийся низкоуровневым 
    // аналогом класса GraphicsDevice.
    IDirect3DDevice9* devicePtr1 = this.pComPtr; 
    // Вызывает COM-метод IDirect3DDevice9::Present, 
    который выполняет переключение 
    // буферов и вывод изображения на экран. К сожалению, 
    .NET Reflector сгенерировал, 
    // мягко говоря, не самый красивый код.
    int num1 = **(((int*) devicePtr1))[0x44](devicePtr1, 0, 0, 0, 0);
    // Если метод IDirect3DDevice9::Present возвратил 
    отрицательное значение, то есть во 
    // время выполнения метода произошла ошибка 
    (в COM отрицательные значения 
    // соответствуют кодам ошибок). if (num1 < 0) 
    { 
    // Если код ошибки равен -2005530520 (соответствует потере устройства)
     if (num1 == -2005530520) 
    { 
    // Подготовка к вызову обработчика события DeviceLost 
    (если он определен) с 
    // последующим вызовом. Тема потери устройства будет 
    рассмотрена в разделе 1.2.4. EventArgs args1 = EventArgs.Empty;
    EventHandler handler1 = this.<backing_store>DeviceLost;
    if (handler1 != null)
    {
    handler1(this, args1); 
    }
    } 
    // Генерируется исключение. Класс исключения и текст сообщения 
    об ошибке определяется 
    // кодом, который вернул Com-метод IDirect3DDevice9::Present
    throw ExceptionHelper.GetExceptionFromResult(num1); 
    } 
    }

    Как видно, метод GraphicsDevice.Present содержит вызов COM -метода IDirect3DDevice9::Present плюс небольшую обвязку для взаимодействия с COM. Иными словами, на платформе Windows метод GraphicsDevice.Present по сути является обвязкой над методом . Впрочем, на других платформах всe может быть совершенно иначе.

    1.2.2. Конфигурирование проектов в Visual Studio 2005

    В этом разделе мы рассмотрим тонкости настройки свойств проекта в среде Visual Studio 2005 с учeтом специфики приложений, использующих XNA Framework. Если вы имеете большой опыт работы с Visual Studio, можете пропустить этот раздел.

    Как вы знаете, самой крупной единицей Visual Studio 2005 является решение ( Solution ), описание которого хранятся в текстовом файле формата XML с расширением .sln. Каждое решение состоит из одного или нескольких проектов: набора файлов исходного кода и ресурсов, которые будут откомпилированы в одну сборку (исполняемый файл .exe или динамическую библиотеку .dll ).Файлы с описанием проектов, использующих язык C#, имеют расширение .csproj. Файлы исходного кода C# -программы имеют расширение .cs, файлов ресурсов - .resx и т.д. В принципе для получения представления о структуре решения достаточно открыть любой проект и взглянуть на вкладку Solution Explorer (рисунок 1.4).

    Примечание

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

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

    Debug и Release.

  • Конфигурация Debug предназначена для отладки приложения. При использовании этой конфигурации приложение компилируется без использования оптимизаций, а в .exe файл добавляются отладочные символы. Благодаря этому отладчик может найти однозначное соответствие между полученным двоичным кодом и исходным текстом программы, что позволяет осуществлять пошаговое выполнение программы, просмотр промежуточных значений переменных и т.п. Кроме того, программист, используя директивы условной компиляции, может добавлять в отладочную версию кода ряд разнообразных дополнительных расширенных проверок вводимых данных.
  • Конфигурация Release применяется для построения финальной версии приложения. В этом случае компилятор применяет различные оптимизации: удаление лишних переменных, перестановка инструкций, разворачивание циклов и т.п. Это значительно повышает производительность приложения (иногда в десятки раз), однако значительно усложняет отладку, которая возможна лишь на уровне машинного кода.
  • Переключение между этими конфигурациями легко осуществляется с использованием выпадающего списка на панели инструментов Visual Studio (рисунок 1.10). Вообще, конфигурации Debug и Release отличаются между собой лишь настройками различных свойств. Теоретически, проигравшись со свойствами конфигурации Debug, вы можете легко превратить еe в функциональный аналог конфигурации Release и наоборот. Кроме того, вы можете добавить в решение некоторые специфические конфигурации вроде "Release Shareware Version", "Release Full Version" и т.д.
  • (рис 1.10) Переключение между различными конфигурациями с использованием панели инструментов

    Примечание

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

    Конфигурация Release в проектах C# по умолчанию настроена довольно оптимально и вам вряд ли придeтся еe менять. К сожалению, этого нельзя сказать о конфигурации Debug. Сейчас мы с вами это исправим. Активируйте конфигурацию Debug при помощи выпадающего списка на панели задач. Щелкните два раза на узле Properties во вкладке Solution, чтобы открыть вкладку свойств проектаЭта вкладка, по сути, является визуальным редактором файла проекта (*.cjproj и т.п.) . (рисунок 1.11).

    (рис 1.11) Свойства проекта

    Для начала щeлкните на закладку Debug и включите флажок Enable unmanaged code debugging. Из названия нетрудно догадаться, что этот флажок включает отладку неуправляемого кода. Зачем это надо? Большинство классов сборки XNA Framework являются тонкими надстройками над COM -компонентами "обычного" DirectXДанное утверждение может быть не верным для платформ, отличных от Windows . В результате при выключенной отладке неуправляемого кода Visual Studio 2005 не может получать информацию от COM -компонентов отладочной версии DirectX и, соответственно, выводить еe в окно Output.

    Попробуйте включить эту опцию в примере Ch01\Ex02 (визуализация шахматной доски) и понаблюдать в окне Output за сообщениями отладочной версии DirectX (рисунок 1.12). Отметьте ощутимо возросшее время загрузки приложения.

    (рис 1.12) Окно Output

    В целом, окно Output в сочетании с опцией Enable unmanaged code Debugging является неплохой интегрированной альтернативой утилите Debug View, хотя и не лишенной ряда недостатков – очень низкой производительности и отсутствия поддержки в Visual C# 2005 Express.

    Внимание

    Даже если вы выбрали конфигурацию Release, Visual Studio при нажатии клавиши F5 ( Start with Debugging ) все равно запускает .NET -приложение под управлением отладчика, что ощутимо снижает быстродействия. Для полного отключения отладки необходимо запустить приложение на выполнение комбинацией клавиш Ctrl + F5 ( Start without debugging ).

    Проверка переполнения

    Вторая полезная опция, Check for arithmetic overflow/underflow, находится в диалоговом окне Advanced Build Setting, открываемом при помощи кнопки Advanced Build Setting в закладке Build (рисунок 1.13). Этот флажок включает проверку целочисленного переполнения для всей программы: если в ходе вычислений результат вдруг выйдет за пределы допустимого диапазона, то программа автоматически сгенерирует исключение System.OverflowException. Эта поможет сэкономить вам много времени и нервов, при поиске трудноуловимых ошибок вроде:

    // Объявляем 16-битную переменную со знаком и присваиваем ей 32767
    short a = 32767;
    // Увеличиваем еe на 5. Из за переполнения переменная a станет 
    равна -32764, а не 32772 !!!
    a = (short) (a + 5);
    // В итоге оператор WriteLine выведет на экран -32764
    Console.WriteLine(a);
    (рис 1.13) Диалоговое окно Advanced Build Setting

    Так как проверка переполнения несколько снижает производительность программы, еe рекомендуется выключать в конфигурации Release. Если же у вас имеется потенциально опасный код, в котором может произойти переполнение, поместите его во внутрь блока checked. Например:

    checked {
    a = (short) (a + 5);
     }

    Остальные особенности конфигурирования проектов C# мы рассмотрим в следующих разделах по мере необходимости. Более подробную информацию по этой теме можно найти в [К.7], [К.8] и [К.9].

    1.2.3. Изменение размеров окна

    Запустите на выполнение нашу программу, рисующую шахматную доску ( Ex02 ) и попробуйте поизменять размеры окна. Думаю, вы быстро заметите, что с программой что-то не так. При уменьшении размеров окна шахматная доска не масштабируется, в результате чего изображение начинает выглядеть как-то странновато (рисунок 1.14). А при увеличении окна изображение шахматной доски искажается непонятным образом (рисунок 1.15).

    (рис 1.15) Некорректная реакция программы на уменьшение размера окна(рис 1.15) Некорректная реакция программы на увеличение размера окна

    Как гласит народная мудрость, за двумя зайцами погонишься – ни одного не поймаешь. Поэтому для начала мы сосредоточимся на первой проблеме – неизменном размере шахматной доски при уменьшении формы. Эта проблема вызвана тем, что по умолчанию Windows Forms не гарантирует вызов события Paint при изменении размеров формы. В принципе, для борьбы с этим недоразумением мы могли бы добавить в обработчик события ResizeСобытие Resize генерируется при изменении размеров формы вызов метода Invalidate, генерирующего событие Paint.

    Однако существует гораздо более элегантное решение: если установить у формы стиль ResizeRedraw, то при изменении размера формы будет автоматически генерироваться событие Paint. Для этого добавьте в обработчик Load строку:

    SetStyle(ControlStyles.ResizeRedraw, true);

    Попробуйте ещe раз запустить программу на выполнение. И что мы видим? Хотя теперь приложение и реагирует на изменение размера окна, появились новая, гораздо более неприятная проблема – при изменении размеров окна форма непрерывно мерцает. Чтобы понять причину этого явления попробуйте изменить цвет формы на зелeный (свойство BackColor ) и снова запустите на выполнение. Мерцания шахматной доски обретут зеленоватый оттенок.

    Итак всe дело в том, что перед вызовом обработчика события Paint класс Form вызывает виртуальный метод OnPaintBackground, который по умолчанию очищает экран цветом BackColor. Эта функциональность позволяет разработчику, использующему GDI+, не заботится об очистке экрана, однако в нашем случае такая "самовольная" очистка формы приводит лишь к мерцанию. Следовательно, нам необходимо каким-нибудь образом запретить форме закрашивать экран перед вызовом обработчика события Paint. Первое, что приходит в голову - перегрузить метод OnPaintBackground protected override void OnPaintBackground(PaintEventArgs pevent)

    {
     // Ничего не делаем 
    }

    Впрочем, если покопаться в документации, можно найти и более изящное решение проблемы. Если установить у формы стиль ControlStyles.Opaque, то форма не будет автоматически закрашивать фон, что собственно нам и нужно: SetStyle(ControlStyles.Opaque, true ); После добавления этой строки в обработчик события Load мерцания наконец-то исчезнут.

    Итак, первую проблему мы решили, но осталась вторая, гораздо более неприятная - некорректное масштабирование шахматной доски при изменении размера окна. На первый взгляд проблема возникает буквально на пустом месте - мы задаeм параметры двойной буферизации и вспомогательных буферов, создаeм новое устройство, после чего перерисовываем экран в обработчике события Paint. Вроде бы ничего противозаконного мы не делаем… Стоп! При создании графического устройства мы задаeм размер вспомогательного буфера, используемого при двойной буферизации, равный размеру клиентской области окна, что вполне логично. Когда мы изменяем размер окна, его клиентская область так же изменяется. А вот размер вспомогательного буфера остаeтся неизменным - мы ведь задаeм его размер только один раз в обработчике события Load. Получается что, при изменении размеров окна происходит рассинхронизация между размером клиентской области окна и вспомогательного буфера, в котором собственно рисуется изображение, после чего приложение естественно начинает работать некорректно.

    Следовательно, нам необходимо добавить в программу коррекцию размера вспомогательного буфера при изменении размера окна. В XNA Framework эта операция выполняется с использованием метода Reset класса GraphicsDevice:

    public void Reset(params PresentationParameters[] 
    presentationParameters);

    где

  • presentationParameters - набор структур PresentationParameters, описывающих новое представление данных на экране. Каждому монитору соответствует своя структура PresentationParameters.
  • Итак, нам придeтся вынести локальную переменную PresentationParameters presentParams за пределы метода Load (то есть сделать еe полем класса MainForm ) и добавить в обработчик события Resize изменение высоты и ширины вспомогательного буфера структуры presentParams с последующим вызовом метода Device.Reset (листинг 1.5).

    private void MainForm_Resize(object sender, EventArgs e)
    {
    // Задаем новые размеры буфера глубины
    presentParams.BackBufferWidth = ClientSize.Width;
    presentParams.BackBufferHeight = ClientSize.Height; 
    // Применяем новые параметры к устройству
    device.Reset(presentParams);
     }

    Примечание

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

    После такой модернизации наша программа наконец-то заработала без ошибок. Хотя так ли это? Как гласит известная пословица, в каждой программе есть как минимум одна ошибка. Наша программа не исключение. Попробуйте из интереса уменьшить еe размер до минимума. Как только высота клиентской области окна станет меньше одного пикселя, в окне Output появятся сообщения от отладочной версии DirectX, а программа аварийно завершится с исключением:

    Direct3D9: (ERROR) :Failed to create driver surface
    Direct3D9: (ERROR) :Reset failed and 
    Reset/TestCooperativeLevel/Release are the only legal
    APIs to be called subsequently
    A first chance exception of type 'Microsoft.DirectX.Direct3D.
    DriverInternalErrorException'
    occurred in Microsoft.DirectX.Direct3D.dll

    Поведение Direct3D вполне логично, ведь попытка вывести изображение на форму с клиентской областью размером менее одного пикселя выглядит, мягко говоря, довольно странной. Однако пользователя такое оправдание вряд ли обрадует, поэтому неплохо бы обезопаситься от подобных казусов, ограничив минимальный размер клиентской области одним пикселем. Это легко можно сделать при помощи свойства MinimumSize, которое задаeт минимальные размеры окна. Правда задание этого свойства во вкладке Properties не самая лучшая идея - область, отводимая формой под клиентскую область, зависит от множества факторов: установленной операционной системы, пользовательских настроек и т.п. Гораздо надeжнее вычислять его прямо в обработчике события Load посредством метода формы SizeFromClientSize, который возвращает размер окна при заданном размере клиентской области:

    // Вычисляем размер окна при клиентской области 1x1 пиксель. 
     Полученное значение присваиваем 
    // свойству MinimumSize 
    MinimumSize = SizeFromClientSize(new Size(1, 1)) ;

    Ещe одной ошибкой стало меньше. Думаю, вы уже убедились, что написать приложение без единой ошибки для такой сложной операционной системы, как Windows,не так уж и просто. Всегда существует вероятность пропустить какой-нибудь нюанс. Например, мы до сих пор не пробовали минимизировать окно при помощи соответствующей стандартной кнопки в правом верхнем углу окна. Попробуйте нажать эту кнопку, и в обработчике события Resize тут же произойдeт исключение - приложение попытается установить размер заднего буфера равным 0 на 0 пикселей. Такое поведение программы обусловлено тем, что при минимизации окна программы Windows уменьшает его размер до нуля пикселей. Следовательно, нам необходимо вставить в обработчик события Resize проверку состояния окна - если окно минимизировано, то программа не должна пытаться изменять размер заднего буфера:

    private void MainFormResize(object sender, EventArgs e) 
    {
    // Если окно не минимизировано, то изменяем размер заднего буфера и сбрасываем устройство 
    if (WindowState != FormWindowState.Minimized) 
       {
           presentParams.BackBufferWidth = ClientSize.Width; 
           presentParams.BackBufferHeight = ClientSize.Height; 
           device.Reset(presentParams); 
        } 
    }

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

    using System;
    using System.Collections.Generic; using System.ComponentModel;
     using System.Data; using System.Drawing; using System.Text;
      using System.Windows. Forms ;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Xna = Microsoft.Xna.Framework;
    using XnaGraphics = Microsoft.Xna.Framework.Graphics;
    namespace GSP.XNA.Book.Ch01.Ex03 
    {
    public partial class MainForm : Form 
    {
    GraphicsDevice device=null; PresentationParameters presentParams ;
    public MainForm()
     {
    InitializeComponent();
     }
    private void MainFormLoad(object sender, EventArgs e) 
    {
    SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
    MinimumSize = SizeFromClientSize(new Size(1, 1));
    presentParams = new PresentationParameters(); 
    presentParams.IsFullScreen = false; presentParams.BackBufferCount = 1; 
    presentParams.SwapEffect = SwapEffect.Discard; 
    presentParams.BackBufferWidth = ClientSize.Width; 
    presentParams.BackBufferHeight = ClientSize.Height;
    device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, 
    DeviceType.Hardware, this.Handle, 
    CreateOptions.SoftwareVertexProcessing | CreateOptions.SingleThreaded,
    presentParams) ; 
    }
    private void MainFormPaint(object sender, PaintEventArgs e) 
    {
    device.Clear(ClearOptions.Target, XnaGraphics.Color.WhiteSmoke, 
    0.0f, 0);
    Xna.Rectangle[] rects = new Xna.Rectangle[32];
    int k = 0;
    for (int j = 0; j < 8; j++)
    for (int i = j % 2; i < 8; i += 2) 
    {
    rects[k] = new Xna.Rectangle(i * ClientSize.Width / 8, Ь  j * 
    ClientSize.Height / 8, ClientSize.Width / 8, ClientSize.Height / 8);
     k++; 
    }
    device.Clear(ClearOptions.Target, XnaGraphics.Color.Brown, 0.0f, 0,
     rects);
    device.Present();
    }
    private void MainForm_Resize(object sender, EventArgs e) 
    {
    if (WindowState != FormWindowState.Minimized) 
    {
    presentParams.BackBufferWidth = ClientSize.Width;
    presentParams.BackBufferHeight = ClientSize.Height;
    device.Reset(presentParams); 
    } 
                     }
    private void MainForm_FormClosed(object sender, FormClosedEventArgs e) 
    {
    if (device != null) 
    {
    device.Dispose(); device = null; 
      } 
    } 
            } 
    }

    Обратите внимание на применение псевдонимов для пространств имен Microsoft.Xna.Framework и Microsoft.Xna.Framework.Graphics , позволившие упростить обращение к структурам Microsoft.Xna.Framework.Color и Microsoft.Xna.Framework.Graphics.Rectangle . Обычная директива using прошлых версий C# в подобных ситуация оказывалась бессильной из-за конфликта с одноименными структурами из пространства имен System.Drawing .

    Практическое упражнение №1.1.

    Создайте приложение, рисующее на экране 10 вложенных разноцветных прямоугольников (рисунок 1.14). Самый крупный прямоугольник должен иметь синий цвет, а самый маленький зелeный. Остальные прямоугольники имеют промежуточные цвета, образуя плавный переход от синего к зелeному. Приложение должно корректно реагировать на изменение размера экрана.

    Примечание

    (рис 1.14) Иллюстрация к практическому упражнению №1.1

    Если у вас возникнут трудности при выполнении этого упражнения, вы можете посмотреть исходный текст готового приложения, которое находится в example.zip в каталоге Ch01\Ex04 .

    Для вычисления промежуточных значений цвета воспользуйтесь конструктором public Color(byte r, byte g, byte b) , создающим структуру Color на основе значений красного, зеленого и синих цветов. Значения цветов находятся в диапазоне 0…255 (0 – минимальная яркость, 255 – максимальная яркость).

    1.2.4. Восстановление работоспособности программы после потери устройства

    Операционная система Windows является многозадачной операционной системой, поэтому параллельно с вашим XNA -приложением могут выполняться десятки других приложений использующих графическую подсистему компьютера. При этом не исключены конфликтные ситуации. Например, какая-нибудь программа может неожиданно изменить разрешение экрана, что вызовет перераспределение видеопамяти, что в свою очередь приведeт к частичной потере информации, хранящейся в видеопамяти. В результате с большой долей вероятности класс GraphicsDevice вашего приложения потеряет информацию в видеопамяти и не сможет продолжать работу. В XNA Framework и DirectX это явление называется потерей устройства ( Device Lost ).

    Для начала нам надо научиться воспроизводить потерю устройства, иначе мы не сможем проверить, как наша программа поведeт себя в случае потери устройства. Я обнаружил, что потеря устройства всегдаЯ не встречал ни одной видеокарты, на которой в этом случае не происходила бы потеря устройства происходит при переключении консольного приложения в полноэкранный режим при помощи клавиш alt + Enter. Следовательно, для форсирования потери устройства вам необходимо:

  • Запустить приложение, использующее DirectX.
  • Запустить консольное приложение. Я предпочитаю пользовать FAR, но если у вас он не установлен, вполне подойдeт и обычная командная строка ( Start | All Programs | Accessories | Command Prompt )
  • Переключиться в полноэкранный режим и обратно в оконный (два раза нажать Alt + Enter ).
  • Если вы проделаете эти операции над примером Ex03 из предыдущего раздел, то он аварийно завершится из-за не перехваченного исключением Microsoft. Xna.Framework.Graphics.DeviceLostException, название которого ясно говорит о причине краха приложения.

    Для определения текущего состояния устройства в классе GraphicsDevice имеется свойство GraphicsDeviceStatus:

    public GraphicsDeviceStatus GraphicsDeviceStatus { get; }

    Свойство возвращает перечислимый тип GraphicsDeviceStatus, который может принимать одно из следующих значений:

  • Normal - устройство функционирует нормально
  • NotReset - устройство потеряно, но может быть восстановлено методом Reset класса GraphicsDevice.
  • Lost - устройство потеряно и пока не может быть восстановлено.
  • Если устройство находится в состоянии NotReset, то его необходимо восстановить, вызвав метод GraphicsDevice.Reset, после чего приложение может продолжать работу, как ни в чeм не бывало. Если же устройство находится в состояние Lost, то тут нечего не поделать - остаeтся лишь выйти из обработчика события Paint и ждать лучших времeн.

    Потеря устройства является очень коварной проблемой, которая может произойти не только до вызова обработчика Paint, но и внутри него. В этом случае XNA Framework в зависимости от ситуации сгенерирует исключения Direct3D.DeviceNotResetException или Direct3D.DeviceLostException. Если сгенерировано исключение Direct3D.DeviceNotResetException, то приложение должно восстановить устройство методом GraphicsDevice.Reset() и снова перерисовать изображение путeм вызова метода Form.Invalidate(), в противном случае просто выйти из обработчика события Paint.

    После всего вышесказанного мы сможем легко научить нашу программу корректно реагировать на потерю устройства. Исходный код обновлeнного обработчика события Paint приведeн в листинге 1.8.

    // Полный текст приложения находится в каталоге Examples\Ch01\Ex05
    private void MainForm_Paint(object sender, PaintEventArgs e)
    {
    try
    { 
    // Если устройство нельзя восстановить, генерируем исключение 
    DeviceLostException (мы его 
    // перехватим в блоке catch)
    Я не встречал ни одной видеокарты, на которой в этом случае не 
    Я происходила бы потеря устройства.
    if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.Lost)
     throw new DeviceLostException();
    // Если устройство можно восстановить, восстанавливаем его
    if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.NotReset) 
    device.Reset();
    // Рисуем шахматную доску
    device.Clear(XnaGraphics.Color.WhiteSmoke) ;
    Xna.Rectangle[] rects = new Xna.Rectangle[32];
    int k = 0;
    for (int j = 0; j < 8; j++)
    for (int i = j % 2; i < 8; i += 2) {
    rects[k] = new Xna.Rectangle(i * ClientSize.Width / 8, Ь  j * 
    ClientSize.Height / 8, ClientSize.Width / 8, ClientSize.Height / 8); 
    k++; 
    }
    device.Clear(ClearOptions.Target, XnaGraphics.Color.Brown, 0.0f, 0,
     rects);
    device.Present();
    } 
    // Если произошло исключение DeviceNotResetException, перерисовываем 
    экран. Восстанавливать 
    // устройство не имеет смысла - наш обработчик события Paint сделает
     это автоматически
    catch (DeviceNotResetException)
    {
    Invalidate();
    } 
    // Если произошло исключение DeviceLostException, то нам остается 
    только ждать до лучших 
    // времен
    catch (DeviceLostException)
    {
    } 
    }

    Практическое упражнение №1.2

    "Обучите" программу, которую вы создали в упражнении №1.1, корректно реагировать на потерю устройства.

    Заключение

    XNA Framework - это высокопроизводительная мультимедийная библиотека для разработки приложений, требовательных к производительности видеоподсистемы и аудиосистемы компьютера, а так же подсистеме ввода-вывода. Одной из причин появления XNA Framework являются некоторые трудности использования Microsoft DirectX на платформе .NET. В отличие от компонентов "обычного" DirectX, сборки Microsoft.Xna.Framework.dll и Microsoft.Xna.Framework.Game.dll соответствуют всем требованиям платформы .NET 2.0 и не привязаны к конкретному языку программирования. В результате эта сборка с одинаковой лeгкостью могут использоваться в любой .NET -совместимой среде программирования вроде Microsoft Visual Basic, Microsoft Visual C# , Microsoft Visual J# или Iron Python. Кроме того, XNA Framework является платформо-независимый библиотекой, что открывает дорогу для переноса приложений на другие платформы, такие как, XBOX 360. Все компоненты XNA Framework условно можно разделить на четыре уровня абстракции: Platform, Core Framework, Extended Framework и Game. В этой лекции мы познакомились с компонентом Graphics (один из компонентов уровня Core Framework ), предназначенным для работы с графической подсистемой компьютера на относительно низком уровне. Плюсом подобного низкоуровневого подхода является высокая производительность приложения, минусом - необходимость учeта разнообразных нюансов вроде коррекции размера вспомогательного буфера при изменении размера окна или и возможности неожиданной потери устройства. Впрочем, даже поверхностные знания основных процессов, творящихся под капотом высокоуровневых графических библиотек, очень полезны при написании надежных высокопроизводительных графических приложений.

    Вернуться к учебному плану