29
Глава 1. Введение в XNA Framework Первые версии Windows позволяли программисту работать с видеоподсистемой компьютера лишь посредством стандартного интерфейса GDI, предоставляющему программисту унифицированный доступ к различным устройствам вывода графической информации будить то видеокарта, принтер или плоттер. Интерфейс GDI очень прост в использовании приложение работает с некоторым виртуальным устройством, а GDI самостоятельно транслирует все вызовы приложения в команды конкретной видеокарты или принтера. Преимущества данного подхода очевидны. Например, добавив в приложение всего несколько строк кода, вы можете с легкостью перенаправить вывод с экрана монитора на принтер и получить нужный результат. Кроме того, работа с виртуальным устройством не позволяет некорректно написанному приложению нанести какой-либо ощутимый ущерб стабильности операционной системе и повредить данные других приложений. Как всегда, недостатки интерфейса GDI являются продолжением его достоинств. Любому человеку, даже поверхностно знакомому с устройством персонального компьютера, ясно, что, к примеру, видеокарта NVIDIA GeForce 5900 Ultra и лазерный принтер Canon Laser Shot LBP-1120 являются абсолютно разными устройствами. Соответственно, при проектировании универсального интерфейса для работы с данными устройствами неминуемо придётся жертвовать эффективностью. Это не особо критично для офисных приложений, работающих с достаточно простыми изображениями и не требовательных к скорости обновления экрана. Однако для целого класса приложений (игры, системы виртуальной реальности, пакеты 3D моделирования), критичных к производительности видеоподсистемы компьютера, накладные расходы GDI оказались неприемлемыми. В результате разработчики этих приложений не стремились переносить свои разработки с DOS на Windows, что отнюдь не способствовало росту популярности операционной системы Windows. OpenGL Так как операционная система без развлекательных приложений вряд ли смогла бы добиться широкой популярности среди домашних пользователей, Microsoft включала сначала в Windows NT 3.5, а затем и в Windows 95 OSR2 поддержку OpenGL – известного кроссплатформенного API для разработки трехмерных графических приложений реального времени. В те времена OpenGL справедливо считался флагманом индустрии трехмерной графики реального времени. Тем не менее, у него все же был ряд недостатков: Так как OpenGL предназначен исключительно для работы с графикой, он решил лишь проблемы низкой производительно графической подсистемы Windows. Работа с аудиоподсистемой компьютера по- прежнему осуществлялась с использованием стандартного медленного интерфейса MCI 1 . Аналогичным образом обстояли дела и получением информации от устройств ввода: клавиатуры, мыши и джойстиков. 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 [С.1]. Это был достаточно примитивный API предназначенный для работы исключительно с двухмерной графикой реального времени в 1 Media Control Interface – интерфейс управления мультимедийными устройствами. Содержит набор стандартных команд, позволяющих осуществлять воспроизведение и запись файлов мультимедийных ресурсов. Информацию о MCI можно найти, к примеру, в [К.4]. 1

Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Embed Size (px)

Citation preview

Page 1: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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

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

Так как OpenGL предназначен исключительно для работы с графикой, он решил лишь проблемы низкой производительно графической подсистемы Windows. Работа с аудиоподсистемой компьютера по-прежнему осуществлялась с использованием стандартного медленного интерфейса MCI1. Аналогичным образом обстояли дела и получением информации от устройств ввода: клавиатуры, мыши и джойстиков.

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 [С.1]. Это был достаточно примитивный API предназначенный для работы исключительно с двухмерной графикой реального времени в

1 Media Control Interface – интерфейс управления мультимедийными устройствами. Содержит набор стандартных команд, позволяющих осуществлять воспроизведение и запись файлов мультимедийных ресурсов. Информацию о MCI можно найти, к примеру, в [К.4].

1

Page 2: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

операционных системах Windows 3.1 и Windows 95. Видеорежимы с количеством цветов более 256 не поддерживались. Единственным преимуществом WinG была более высокая производительность, чем у GDI. Впрочем, на фоне DOS и OpenGL 1.1 оно выглядело весьма спорным. После неудачи с WinG стало ясно, что Microsoft вряд ли сможет в одиночку в короткие сроки разработать конкурентоспособный API для программирования графических приложений реального времени. В результате было принято решение купить британскую компанию RenderMorfics и перенести её библиотеку Reality Lab на платформу Windows. Так появился Game SDK, позже переименованный в DirectX. Из-за сжатых сроков Microsoft не смогла создать на основе Reality Lab полноценный масштабируемый API с заделом на будущее, в результате чего на рынке началась настоящая чехарда версий DirectX, для получения представления о масштабах которой достаточно посмотреть на частоту появления новый версий DirectX (таблица 1.1). Нетрудно заметить, что каждый год выходило не менее одной версии DirectX, причём новая версия содержала множество кардинальных изменений, в результате чего оказывалась несовместимой с более старой версией. Положение стабилизировалось лишь в 2002-м году с выходом 9-й версии DirectX. С тех пор были выпущены лишь три новых редакции DirectX с незначительными изменениями, после чего Microsoft перешла к выпуску обновлений к DirectX, устраняющих мелкие недочеты, в темпе одно обновление каждые два месяца.

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

Таблица 1.1. Даты выхода версий 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 лежит набор COM1-интерфейсов, предоставляющих программисту доступ к аппаратному обеспечению компьютера. Эти интерфейсы разработаны Microsoft в тесном сотрудничестве с ведущими производителями аппаратных устройств, таких как Intel, ATI, NVIDIA, Creative и т.д. Поэтому интерфейсы DirectX очень близки к современному аппаратному обеспечению и фактически связаны с аппаратным обеспечением через тонкую прослойку драйверов, минуя стандартные интерфейсы Win32, такие как GDI и MCI. В DirectX 9 интерфейсы сгруппированы в три специализированных компонента2 (рисунок 1.1):

DirectX Graphics, отвечающий за работу с двухмерной и трёхмерной графикой. DirectInput, предназначенный для работы с устройствами ввода: мышью, клавиатуры, джойстиками с обратной связью и т.п.

DirectSound, используемый для работы со звуковым оборудованием: звуковыми картами, в том числе и с поддержкой трёхмерного звука, MIDI-синтезаторами и т.п.

1 Component Object Model – модель компонентных объектов. Определяет стандарты интерфейсов API и бинарные стандарты для связи объектов, не зависящих от языка программирования. Каждый объект COM имеет один или несколько интерфейсов, представляющих собой таблицы функций, связанных с этим объектом. На рынке имеется множество литературы, посвящённой COM начиная с [К.9] и заканчивая MSDN. 2 В действительности в составе DirectX имеется ещё четыре компонента: DirectDraw (работа с двухмерной графикой), DirectMusic (проигрывание фоновой музыки), DirectPlay (работа с сетью) и DirectShow (проигрывание видеофайлов). Однако эти компоненты не рекомендуются к использованию, так как они объявлены устаревшими и по видимости будут исключены из будущих версий DirectX.

2

Page 3: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Рисунок 1.1. Упрощённая схема взаимодействия приложения с устройствами при использовании DirectX

Managed DirectX После выхода платформы .NET Framework встал вопрос об использование трехмерной графики в приложениях, написанных на управляемых языках вроде C#. Дело в том, что COM-интерфейсы компонентов DirectX проектировались в расчёте на использование в программах на языке C++, в результате чего они активно используют специфические возможности C++, к примеру, указатели. Поэтому, хотя язык C# и позволяет использовать COM-компоненты, применение интерфейсов DirectX в программах на C# сопряжено с рядом проблем: применение же указателей в C# не приветствуется и является плохим тоном программирования на платформе .NET, указатели могут использоваться только в unsafe-блоках, что затрудняет чтение программы и повышает вероятность ошибок. Вдобавок, COM-компоненты не могут использовать преимущества инфраструктуры .NET такие как автоматическая сборка мусора и обработка ошибок с использованием исключений. Чтобы облегчить жизнь разработчикам приложений для платформы .NET, Microsoft включила в состав 9-й версии DirectX надстройку над COM-интерфейсами DirectX: Managed DirectX. В октябре 2005 года была анонсирована вторая версия Managed DirectX, которая была фактически переписана с нуля с учётом новых возможностей платформы .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 состоит из трёх компонентов: Direct3D Graphics, DirectInput и DirectSound. Каждому компоненту соответствует одно или несколько пространств имён (таблица 1.2), содержащих классы и структуры данного компонента. При этом хорошо прослеживается соответствие между классами DirectX и соответствующими COM-интерфейсами неуправляемого DirectX. К примеру, COM-интерфейсу IDirect3D9 соответствует брат-близнец класс Microsoft.DirectX.Direct3D.Device, интерфейсу IDirect3DTexture9 – класс Microsoft.DirectX.Direct3D.Texture и так далее. Более того, при должной сноровке при изучении Managed DirectX вполне можно пользоваться документацией по C++ и наоборот1.

Таблица 1.2. Пространства имён Managed DirectX 2.0

Пространство имён Соответствующий компонент DirectX

Описание

Microsoft.DirectX Используется всеми компонентами DirectX

Содержит базовый набор классов, общий для всех компонентов: векторы, матрицы, кватернионы и

1 Это очень важный нюанс, так как в настоящее время классический DirectX содержит гораздо более подробную документацию.

3

Page 4: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

т.п.

Microsoft.DirectX.Generic Используется всеми компонентами DirectX

В этом пространстве имён расположены все обобщённые классы

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 Работа со звуком, в том числе и трёхмерным.

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

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

приложений баз данных, web-сервисов, web-сайтов. Разумеется, данная функциональность является, мягко говоря, несколько избыточной для игровой приставки. Кроме того, ряд классов .NET Framework сильно привязаны к операционной системе Windows (пространства имен System.Windows.Forms, System.Drawings), а перенос Windows на игровую приставку является весьма сомнительной затеей.

2. Managed DirectX, является достаточно тонкой настройкой над DirectX. А так как DirectX – это один из компонентов платформы Windows, Managed DirectX автоматически оказывается непереносимым API, привязанным к платформе Windows. Кроме того, классы и структуры Managed DirectX активно используют функциональность из пространств имен System.Windows.Forms и System.Drawing.

Первая проблема была решена путём реализации на 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 (работа с устройствами

4

Page 5: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

ввода-вывода), Math (математические расчеты), Storage (работа с файловой системой). Классы и структуры каждого компонента сгруппированы в пространства имен (таблица 1.3). На платформе Windows первые три компонента (Graphics, Audio, Input) являются надстройками над DirectX, а компонент Storage – надстройкой над классами .NET Framework для работы с файловой системой. Однако следует всегда помнить о том, что на других платформах всё может обстоять совершенно иначе.

Extended Framework (расширенный каркас) – набор высокоуровневых классов, решающих типовые задачи, встающие перед разработчиком игр: инициализация графического устройства, организация цикла обработки сообщений, экспорт моделей и текстур из графических редакторов. По сути Extended Framework можно считать универсальным игровым движком (Game Engine) начального уровня. Размещается в сборке Microsoft.Xna.Framework.Game.dll.

Game – собственно приложение пользователя, то есть наши с вами программы. К слову, в комплект XNA входит несколько простых игр (Starter Kits), которые можно использовать в качестве заготовок для своих приложений.

PlatformКомпоненты: Direct3D, XACT, XINPUT, XContent и т.д.

Core Framework(Microsoft.Xna.Framework.dll)

Компоненты: Graphics, Audio, Input, Math, Storage

Extended Framework(Microsoft.Xna.Framework.Game.dll)

Компоненты: Application Model, Content Pipeline

Games

Рисунок 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 Graphics Работа с графикой

Microsoft.Xna.Framework.Graphics.PackedVector Graphics Работа с упакованными векторами. Примером упакованного вектора является 32-х битное число, содержащее информацию о яркости красной, синей и зеленой компонентах цвета.

Microsoft.Xna.Framework.Audio Audio Работа со звуком

5

Page 6: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Microsoft.Xna.Framework.Input Input Работа с устройствами ввода (клавиатура, мышь, джойстики).

Microsoft.Xna.Framework.Storage Storage Работа с файловой системой текущей платформы: загрузка и сохранение настроек приложения, “сохраненных игр” (Save Games) и т.д.

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

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

Рисунок 1.3. Наша первое приложение(Ex01), использующее XNA.

Для начала запустите Microsoft Visual Studio 2005 и создайте новый проект GUI–приложения для платформы Windows (File | New | Project...). В раскрывшемся окне выберите Visual C# | Windows | Windows Application, снимите флажок Create directory for Solution и нажмите Ok). Переименуйте файл формы из Form1.cs в MainForm.cs2. Следующий шаг – подключение сборки Microsoft.Xna.Framework.dll, содержащий компоненты слоя Core Framework, включая необходимый нам компонент Graphics.Для подключения сборки щёлкните правой кнопкой мыши на узле Reference в окне Solution Explorer и выберите в контекстном меню пункт Add Reference... (рисунок 1.4). В открывшемся окне выберете сборку Microsoft.Xna.Framework и нажмите кнопку Ok (рисунок 1.5).

1 Graphic User Interface – графический пользовательский интерфейс 2 Во всех примерах книги главная форма приложения будет называться MainForm.cs.

6

Page 7: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Рисунок 1.4. Вкладка Solution Explorer

Рисунок 1.5. Окно Add Reference

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

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

7

Page 8: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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. Значения перечислимого типа Direct3D.DeviceType

Значение Описание

Hardware Аппаратное устройство

Reference Устройство эмулируется средствами DirectX SDK. Обычно используется для эмуляции функциональности, не поддерживаемой текущей видеокартой (например, эмуляция пиксельных шейдеров на GeForce2). Правда, такую эмуляцию реально использовать лишь в отладочных целях, так как производительность приложений в этом режиме редко превышает один кадр в секунду.

NullReference Нуль-устройство, игнорирующее все поступающие команды (наподобие устройства NUL в MS-DOS и Windows). Может использоваться для оценки производительности приложения на бесконечно быстрой видеокарте.

Таблица 1.5. Значения перечислимого типа Direct3D.CreateFlags

Значение Описание

HardwareVertexProcessing Аппаратная обработка вершин средствами GPU1

SoftwareVertexProcessing Программная обработка вершин средствами CPU.

MixedVertexProcessing Смешанная обработка вершин. Позволяет приложению самостоятельно переключаться между программной и

1 Graphic Processor Unit – графический процессор, установленный на видеокарте.

8

Page 9: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

аппаратной обработкой вершин. Полезна, к примеру, при использовании вершинных шейдеров версии 2.0 на GeForce3, который аппаратно поддерживает только вершинные шейдеры версии 1.1. В этом случае вершинные шейдеры версии 1.1 можно выполнять аппаратно, а версии 2.0 – в режиме программной эмуляции.

NoWindowChanges Устройство не будет автоматически восстанавливаться после потери фокуса окном и т.д. Все эти действия ложатся на плечи программиста. Данное значение применяется очень редко.

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

SinglePrecision Переключает математический сопроцессор в режим пониженной точности. Все вычисления с плавающей точкой, включая использующие тип double, будут выполнять с точностью 7 знаков. Более подробная информация об этом режиме приведена во врезке.

Таблица 1.6. Некоторые свойства структуры PresentationParameters

Поле Описание

bool IsFullScreen При выводе на поверхность компонента или формы этому свойству необходимо присвоить значение false. Если же приложение является полноэкранным, то используется значение true.

int BackBufferCount Задаёт количество вспомогательных буферов, используемых для борьбы с эффектом мерцания при смене кадров. При использовании одного вторичного буфера изображение сначала рисуется во вспомогательном буфере, после чего уже готовое изображение копируется в экранный буфер. Этот процесс называется двойной буферизацией. Буферизация, использующая два вспомогательных буфера, называется тройной. Более подробно различные типы буферизации будут рассмотрены в соответствующих разделах книги. А пока мы будем использовать двойную буферизацию, присваивая полю BackBufferCount значение 1.

int BackBufferWidth Ширина вспомогательных буферов в пикселях. Если этот параметр равен 0, то конструктор метода Device рассчитывает его автоматически, полагая равным ширине клиентской области окна1.

int BackBufferHeight Высота вспомогательных буферов в пикселях. Если этот параметр равен 0, то конструктор метода Device рассчитывает его автоматически, полагая равным высоте клиентской области окна.

Direct3D.SwapEffect SwapEffect Указывает, каким образом осуществляется переключение буферов при двойной и тройной буферизации. Подавляющее большинство программ присваивают этому полю значение SwapEffect.Discard – в этом случае видеокарта сама выбирает режим переключения буфера, наиболее подходящий в конкретной ситуации.

Точность вычислений с плавающей точкой процессоров архитектуры x86 Математический сопроцессор архитектуры x87 содержит восемь 80-ти битных регистров общего назначения, используемых для хранения операндов и результатов вычислений. Иными словами, независимо от используемых типов данных сопроцессор всегда оперирует с 80-ти битным форматом с плавающей 1 Часть окна, используемая приложением для вывода информации. В клиентскую область окна не входят заголовок окна, рамки по краям окна и т.д.

9

Page 10: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

точкой, известным как extended double. Если операнды имеют меньшую разрядность (например, используется тип float), то они автоматически конвертируются в 80-ти битный формат, а результат перед копированием в память переводится обратно в 32-х битный формат float. Так как подобная точность вычислений оказывается излишней, в одном из управляющих регистров процессора (CW) имеются два бита (PC), управляющие точностью вычислений. В зависимости от значения битов, задающихся при запуске приложения, вычисления выдуться с 7-ю, 16-ю или 19-ю значащими цифрами. Ещё раз хочу подчеркнуть важную деталь. Формат чисел в регистрах всегда остаётся 80-ти битным, просто с понижением точности младшие разряды числа с плавающей точкой могут содержать недостоверную информацию. Два бита PC регистра CW устанавливаются при запуске потока (thread) и, как правило, не меняются в процессе выполнения приложения. В то же время, параметр CreateOptions.SinglePrecision приказывает конструктору класса GraphicsDevice изменить служебный регистр CW таким образом, чтобы установить внутри сопроцессора точность вычислений 7 знаков. В результате, после вызова данного метода вычисления, включая использующие типы double, будут выполняться с 7-ю значащими знаками. Правда существуют и исключения: так, к примеру, биты PC регистра CW оказывают влияние на точность сложения, вычитания, умножения, деления и вычисления квадратного корня, но совершенно не затрагивают точность команд вычисления синуса и косинуса. В общем, здесь очень много различных тонкостей, но разбираться в них, в общем-то, не нужно. Достаточно запомнить, что флаг CreateOptions.SinglePrecision таит много подводных камней, поэтому применять его стоит только при реальной необходимости поднять производительно математической подсистемы любой ценой. В остальных случаях вы лишь повысите вероятность возникновения различных трудно обнаруживаемых ошибок. Ведь никто не сможет гарантировать, что одна из используемых вами библиотек не начнёт вести себя как-то странно, столкнувшись с аномально низкой точностью вычислений, использующих типы double. Например, пониженная точность может очень негативно сказаться на качестве генератора псевдослучайных чисел. И так, для создания графического устройства мы должны объявить в тексте класса формы поле класса GraphicsDevice: GraphicsDevice device=null;

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

Листинг 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 автоматически рассчитает // значение этих полей и занесёт их в структуру presentParams. Поэтому эти две строки, в // принципе, можно и опустить. presentParams.BackBufferWidth = ClientSize.Width; presentParams.BackBufferHeight = ClientSize.Height; // Создаём новое устройство, обладающее следующими характеристиками: // - Устройство будет использовать видеоадаптер по умолчанию // - Устройство будет аппаратным // - Вывод будет осуществляться на поверхность текущей формы // – Обработка вершин будет осуществляться средствами GPU // - Представление данных на экране задаётся структурой presentParams (см. выше) // – Доступ к устройству возможен только из одного потока приложения

1 Во всех примерах книги главная форма приложения называется MainForm

10

Page 11: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, Handle, CreateOptions.SoftwareVertexProcessing | CreateOptions.SingleThreaded, presentParams); }

Создав объект устройства, можно приступать к реализации закраски формы синим цветом. Для этого воспользуемся методом Clear1 класса GraphicsDevice, который очищает форму путём закраски её заданным цветом: public void Clear(ClearOptions options, Color color, float depth, int stencil);

где options – набор битовых флагов, указывающих какие буферы необходимо очистить. Для очистки экранного буфера используется флаг ClearOptions.Target. Остальные флаги ClearOptions.DepthBuffer и ClearOptions.Stencil, используемые для очистки соответственно буфера глубины и буфера шаблона, которые будут рассмотрены в следующих главах.

color – цвет, которым будет закрашен экран. Задаётся с использованием структуры 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 MainForm_Paint(object sender, PaintEventArgs e) { device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue); }

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

Листинг 1.2.

private void MainForm_FormClosed(object sender, FormClosedEventArgs e) { // Если устройство существует if (device != null) { // Удаляем (освобождаем) устройство device.Dispose(); 1 Практически все классы XNA Framework, включая GraphicsDevice, имеют множество перегрузок (override) конструкторов и методов (иногда более десятка) “на все случаи жизни”. В этой в этой книге будут рассматриваться лишь наиболее распространённые и общие из них – информацию об остальных перегруженных методах вы легко сможете найти в справочной системе.

11

Page 12: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

// На всякий случай присваиваем ссылке на устройство значение null device = null; } }

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

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

Вроде бы всё. Давайте попробуем запустить полученное приложение на выполнение (клавиша F5). Не смотря на то, что метод Clear вызывается при каждой перерисовке окна (в этом легко убедится, установив точку останова на строку с вызовом этого метода при помощи клавиши F9) , на внешнем виде формы это никак не отражается. Интересно, с чем это может связано? Всё дело в том, что мы используем двойную буферизацию, то есть наше приложение выполняет все графические построения в невидимом вспомогательном буфере. После окончания визуализации необходимо скопировать информацию из этого вспомогательного буфера на форму. Эту операцию выполняет метод Present класса GraphicsDevice: void Present()

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

1.2. Визуализация шахматной доски. Одна из перегрузок метода GraphicsDevice.Clear позволяет очищать не весь экран целиком, а лишь заданную прямоугольную область формы: public void Clear(ClearOptions options, Color color, float depth, int stencil, Rectangle[] regions);

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

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

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

Листинг 1.3.

// Закрашиваем экран синим цветом

device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

// Создаём массив с координатами областей экрана, которые необходимо закрасить. Нам

// нужна всего одна область

Microsoft.Xna.Framework.Rectangle[] rect = new Microsoft.Xna.Framework.Rectangle[1];

// Задаём координаты области экрана, расположенной в центре экрана и занимающей 25%

// площади экрана

12

Page 13: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

rect[0] = new Microsoft.Xna.Framework.Rectangle(ClientSize.Width/4,

ClientSize.Height/4, ClientSize.Width/2, ClientSize.Height/2);

// Закрашиваем эту область зелёным цветом

device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.Graphics.Color.Green, 0.0f,

0, rect);

Рисунок 1.6. Зелёный квадрат на синем фоне, нарисованный с использованием метода Clear.

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

Рисунок 1.7. Шахматная доска, нарисованная с использованием метода Clear

Листинг 1.4.

// Полный код приложения находится в каталоге Examples\Ch01\Ex02 private void MainForm_Paint(object sender, PaintEventArgs e) { // Очищаем экран белым цветом device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke); // Создаём массив областей закраски, соответствующих коричневым клеткам Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32]; int k = 0; // Перебираем коричневые клетки шахматной доски for (int j = 0; j < 8; j++)

13

Page 14: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

for (int i = j % 2; i < 8; i += 2) { // Заносим в массив координаты очередной клетки rects[k] = new Microsoft.Xna.Framework.Rectangle(i * ClientSize.Width / 8, 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, 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. Внешний вид этой утилиты изображён на рисунке 1.8. Как видно, данная утилита представляет собой обычное диалоговое окно с набором вкладок, отвечающих за настройку различных компонентов DirectX. Рассмотрим наиболее важные из них.

14

Page 15: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Рисунок 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 и правильности выполнения этих методов. Информация об различных подозрительных ситуациях и ошибках передаётся в отладчик (например, в отладчик Visual Studio 2005). При разработке и отладке приложений рекомендуется всегда использовать отладочную версию Direct3D Graphics. Так же полезно установить ползунок Debug Output Level, отвечающий за подробность отладочной информации в крайнее правое положение, чтобы получать информацию о любых подозрительных ситуациях. Ведь согласно “эффекту бабочки”, даже самый безобидный на первый взгляд недочёт может привести к каскаду трудноуловимых ошибок. В группе Debugging желательно включить следующие флажки: Maximum Validation (максимальная проверка корректности параметров, передаваемых классам Direct3D Graphics), Enable Shader Debugging (отладка шейдеров) и Break on Memory Leaks (обнаружение утечек памяти).

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

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

Рисунок 1.9. Приложение Debug View

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

15

Page 16: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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 указано время поступления отладочного сообщения, что облегчает идентификацию сообщений. По умолчанию используется относительное время – за точку отсчёта берется время поступления первого события, т.е. время наступление первого события всегда равно 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.

16

Page 17: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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 D3DCREATE_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, напротив, по умолчанию создаёт графическое устройство с поддержкой многопоточности, а отключение данной функциональности осуществляется путем указания флага CreateOptions.SingleThreaded. Кроме того, отладочная версия Direct3D нечего не знает о .NET Framework – вместо того, чтобы сообщить о проблемах из-за удаления объекта GraphicsDevice сборщиком мусора она просто жалуется на странное поведение непонятно откуда взявшегося дополнительного потока. Впрочем, получив некоторый опыт чтения сообщений отладочной версии Direct3D, вы перестанете обращать внимание на подобные нюансы.

Взаимодействие XNA Framework c DirectX. Как говорилось выше, XNA Framework по сути является прослойкой между .NET и DirectX. Но насколько эта тонка прослойка и оказывает ли она существенное влияние на производительность приложения? Чтобы ответить на этот вопрос мы рассмотрим работу XNA Framework на примере метода Direct3D.Device.Present, декомпилировав его с использованием .NET Reflector, который находится на CD с книгой в каталоге Tools\NET Reflector1: 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) {

1 Конечно, C#-код, генерируемый .NET Reflector, далёк от идеала. Тем не менее, его код несоизмеримо проще анализировать по сравнению с ассемблерным IL-кодом.

17

Page 18: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

// Если код ошибки равен -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 по сути является обвязкой над методом . Впрочем, на других платформах всё может быть совершенно иначе.

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

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

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

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

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

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

18

Page 19: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

наоборот. Кроме того, вы можете добавить в решение некоторые специфические конфигурации вроде “Release Shareware Version”, “Release Full Version” и т.д.

Рисунок 1.10. Переключение между различными конфигурациями с использованием панели инструментов

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

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

Рисунок 1.11. Свойства проекта

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

1 Эта вкладка, по сути, является визуальным редактором файла проекта (*.cjproj и т.п.)2 Данное утверждение может быть не верным для платформ, отличных от Windows.

19

Page 20: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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

Рисунок 1.12. Окно Output.

В целом, окно Output в сочетании с опцией Enable unmanaged code Debugging является неплохой интегрированной альтернативой утилите Debug View (см. раздел 1.1.1), хотя и не лишенной ряда недостатков – очень низкой производительности и отсутствия поддержки в 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; // Увеличиваем её на 5. Из за переполнения переменная a станет равна -32764, а не 32752 !!! a = (short) (a + 5); // В итоге оператор WriteLine выведет на экран -32764 Console.WriteLine(a);

Рисунок 1.13. Диалоговое окно Advanced Build Setting

20

Page 21: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Так как проверка переполнения несколько снижает производительность программы, её рекомендуется выключать в конфигурации Release. Если же у вас имеется потенциально опасный код, в котором может произойти переполнение, поместите его во внутрь блока checked. Например: checked { a = (short) (a + 5); }

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

1.2.3. Изменение размеров окна Запустите на выполнение нашу программу, рисующую шахматную доску (Ex02) и попробуйте поизменять размеры окна. Думаю, вы быстро заметите, что с программой что-то не так. При уменьшении размеров окна шахматная доска не масштабируется, в результате чего изображение начинает выглядеть как-то странновато (рисунок 1.13). А при увеличении окна изображение шахматной доски искажается непонятным образом (рисунок 1.14). Как гласит народная мудрость, за двумя зайцами погонишься – ни одного не поймаешь. Поэтому для начала мы сосредоточимся на первой проблеме – неизменном размере шахматной доски при уменьшении формы. Эта проблема вызвана тем, что по умолчанию Windows Forms не гарантирует вызов события Paint при изменении размеров формы. В принципе, для борьбы с этим недоразумением мы могли бы добавить в обработчик события Resize1 вызов метода Invalidate, генерирующего событие Paint.

Рисунок 1.13. Некорректная реакция программы на уменьшение размера окна.

Рисунок 1.14. Некорректная реакция программы на увеличение размера окна

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

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

1 Событие Resize генерируется при изменении размеров формы

21

Page 22: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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

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

После добавления этой строки в обработчик события Paint мерцания наконец-то исчезнут. И так, первую проблему мы решили, но осталась вторая, гораздо более неприятная – некорректное масштабирование шахматной доски при изменении размера окна. На первый взгляд проблема возникает буквально на пустом месте – мы задаём параметры двойной буферизации и вспомогательных буферов, создаём новое устройство, после чего перерисовываем экран в обработчике события Paint. Вроде бы ничего противозаконного мы не делаем… Стоп! При создании графического устройства мы задаём размер вспомогательного буфера, используемого при двойной буферизации, равный размеру клиентской области окна, что вполне логично. Когда мы изменяем размер окна, его клиентская область так же изменяется. А вот размер вспомогательного буфера остаётся неизменным – мы ведь задаём его размер только один раз в обработчике события Load. Получается что, при изменении размеров окна происходит рассинхронизация между размером клиентской области окна и вспомогательного буфера, в котором собственно рисуется изображение, после чего приложение естественно начинает работать некорректно. Следовательно, нам необходимо добавить в программу коррекцию размера вспомогательного буфера при изменении размера окна. В XNA Framework эта операция выполняется с использованием метода Reset класса GraphicsDevice: public void Reset(params PresentationParameters[] presentationParameters);

где presentationParameters – набор структур PresentationParameters, описывающих новое представление данных на экране. Каждому монитору соответствует своя структура PresentationParameters.

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

Листинг 1.5.

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

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

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

22

Page 23: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Попробуйте из интереса уменьшить её размер до минимума. Как только высота клиентской области окна станет меньше одного пикселя, в окне 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, которое задаёт минимальные размеры окна. Правда задание этого свойства во вкладке Properties не самая лучшая идея – область, отводимая формой под клиентскую область, зависит от множества факторов: установленной операционной системы, пользовательских настроек и т.п. Гораздо надёжнее вычислять его прямо в обработчике события Load, определяя размеры окна, при которых клиентская область будет иметь размер 1x1 пиксель: MinimumSize = new System.Drawing.Size(Width-ClientSize.Width+1, Height-ClientSize.Height+1);

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

Листинг 1.6.

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

Вот теперь наша программа похоже уже не содержит явных ошибок1. Полный текст полученного приложения приведён в листинге 1.7 (Ex03).

Листинг 1.7.

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;

1 Некоторые ошибки всё же остались, и мы в этом убедимся в следующем разделе (1.2.4).

23

Page 24: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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 MainForm_Load(object sender, EventArgs e)

{

SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);

MinimumSize = new System.Drawing.Size(Width - ClientSize.Width + 1,

Height - ClientSize.Height + 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 MainForm_Paint(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();

}

24

Page 25: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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.15). Самый крупный прямоугольник должен иметь синий цвет, а самый маленький зелёный. Остальные прямоугольники имеют промежуточные цвета, образуя плавный переход от синего к зелёному. Приложение должно корректно реагировать на изменение размера экрана.

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

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

25

Page 26: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

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

1.2.4. Восстановление работоспособности программы после потери устройства. Операционная система Windows является многозадачной операционной системой, поэтому параллельно с вашим XNA–приложением могут выполняться десятки других приложений использующих графическую подсистему компьютера. При этом не исключены конфликтные ситуации. Например, какая-нибудь программа может неожиданно изменить разрешение экрана, что вызовет перераспределение видеопамяти, что в свою очередь приведёт к частичной потере информации, хранящейся в видеопамяти. В результате с большой долей вероятности класс GraphicsDevice вашего приложения потеряет информацию в видеопамяти и не сможет продолжать работу. В XNA Framework и DirectX это явление называется потерей устройства (Device Lost). Для начала нам надо научиться воспроизводить потерю устройства, иначе мы не сможем проверить, как наша программа поведёт себя в случае потери устройства. Я обнаружил, что потеря устройства всегда1 происходит при переключении консольного приложения в полноэкранный режим при помощи клавиш ALT + Enter. Следовательно, для форсирования потери устройства вам необходимо: 1. Запустить приложение, использующее DirectX. 2. Запустить консольное приложение. Я предпочитаю пользовать FAR, но если у вас он не установлен,

вполне подойдёт и обычная командная строка (Start | All Programs | Accessories | Command Prompt)

3. Переключиться в полноэкранный режим и обратно в оконный (два раза нажать Alt + Enter). Если вы проделаете эти операции над примером Ex03 из предыдущего раздел, то он аварийно завершится из-за не перехваченного исключением Microsoft.Xna.Framework.Graphics.DeviceLostException, название которого ясно говорит о причине краха приложения. Для определения текущего состояния устройства в классе GraphicsDevice имеется свойство GraphicsDeviceStatus: public GraphicsDeviceStatus GraphicsDeviceStatus { get; }

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

Normal – устройство функционирует нормально NotReset – устройство потеряно, но может быть восстановлено методом Reset класса GraphicsDevice.

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

Листинг 1.8.

// Полный текст приложения находится в каталоге Examples\Ch01\Ex05 private void MainForm_Paint(object sender, PaintEventArgs e) { try

1 Я не встречал ни одной видеокарты, на которой в этом случае не происходила бы потеря устройства.

26

Page 27: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

{ // Если устройство нельзя восстановить, генерируем исключение 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, корректно реагировать на потерю устройства.

27

Page 28: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Заключение XNA Framework – это высокопроизводительная мультимедийная библиотека для разработки приложений, требовательных к производительности видеоподсистемы и аудиосистемы компьютера, а так же подсистеме ввода-вывода. Одной из причин появления XNA Framework являются некоторые трудности использования Microsoft DirectX на платформе .NET. В отличие от компонентов “обычного” DirectX, сборки Microsoft.Xna.Framework.dll и Microsoft.Xna.Framework.Game.dll соответствуют всем требованиям платформы .NET 2.0 и не привязаны к конкретному языку программирования. В результате эта сборка с одинаковой лёгкостью могут использоваться в любой .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), предназначенным для работы с графической подсистемой компьютера на относительно низком уровне. Плюсом подобного низкоуровневого подхода является высокая производительность приложения, минусом – необходимость учёта разнообразных нюансов вроде коррекции размера вспомогательного буфера при изменении размера окна или и возможности неожиданной потери устройства. Впрочем, даже поверхностные знания основных процессов, творящихся под капотом высокоуровневых графических библиотек, очень полезны при написании надежных высокопроизводительных графических приложений. – Автор выражает благодарность:

Корпорации ATI, которая предоставила видеокарты ATI Radeon 9700 Pro и ATI Radeon 9800 XT для тестирования примеров от книги.

Корпорации NVIDIA, предоставившей видеокарту NVIDIA GeForce 5900 Ultra. Корпорации Microsoft, предоставившей Microsoft Visual Studio .NET 2003 Professional и Microsoft Visual

Studio 2005, а так же Microsoft Windows 2003 Server. Издательству BHV, которое предоставило англоязычную литературу по 3D графике.

Так же автор благодарит Алексея Кряжева (Codemasters Software), Игоря Рыбинского (BHV), Филиппа Герасимова (NVIDIA), Андрея Крючкова (Microsoft), Александра Ложечкина (Microsoft), Олега Михайлика, Евгения Маркова (NVIDIA) и Кирилла Дмитриева (NVIDIA) за консультации и полезные советы. Отдельная благодарность выражается Геннадию Ригеру (AMD) и Юрию Уральскому (NVIDIA), оказавшими неоценимую помощь при написании книги. В заключение хочу поблагодарить сотрудников ВЦ ПГУ Дмитрия Ларина, Дениса Белолапоткова и Павла Маркина, предоставивших мне внутренние резервы Интернет-трафика для скачивания новых версий Microsoft DirectX SDK, XNA Framework, драйверов, патчей, презентаций и т.д.

28

Page 29: Глава 1. Введение в XNA Framework - xnadev.ruxnadev.ru/_Content/GSAF/Ch01.pdf · Преимущества данного ... Windows 3.1 и Windows ... управлением

Список использованной литературы Книги 1. Фень Юань. Программирование графики для Windows 2. Том Миллер. Managed DirectX. Программирование графики и игр 3. Станислав Горнаков. DirectX 9. Уроки программирования на C++ 4. Н. Секунов. Обработка звука на PC 5. В.М. Михальчук, А.А. Ровдо, С.В. Рыжков. Микропроцессоры 80x86, Pentium. Архитектура,

функционирование, оптимизация кода. 6. Ровдо А.А. Микропроцессоры от 8086 до Pentium III Xeon и AMD-К6-3. 7. Джон Роббинс. Отладка приложений для Microsoft .NET и Microsoft Windows 8. Брайан Джонсон, Крейг Скибо, Марк Янг. Основы Microsoft Visual Studio .NET 2003 9. Симон Робинсон и др. C# для профессионалов. 10. Ксавье Пачеко. Delphi for .NET. Руководство разработчика. 11. Михаил Краснов. DirectX. Графика в проектах Delphi 12. Крис Касперски. Техника оптимизации программ. Эффективное использование памяти. 13. Guennadi Riguer. Performance Optimization Techniques for ATI Graphics Hardware with DirectX® 9. 14. Guennadi Riguer. The Radeon X1000 Series Programming Guide. 15. И.И. Кантор. Высокоскоростные железнодорожные магистрали.

Статьи 1. AlexandreS T. Direct X от WinG до Fahrenheit. www.3dnews.ru 2. Константин Максимов. Совершенство графики в GDI+. Программист №2, 2002

Презентации 1. Mitch Walker. Creating Games with the XNA Framework. Gamefest’2006

29