Опубликован: 28.04.2009 | Доступ: свободный | Студентов: 1840 / 107 | Оценка: 4.36 / 4.40 | Длительность: 16:40:00
Специальности: Программист
Лекция 2:

Визуализация примитивов

2.3.3. Использование эффектов в XNA Framework

Одним из основных классов XNA Framework, предназначенным для работы с эффектами, является класс Effect. Класс Effect является довольно сложным классом, содержащим ряд коллекций, отражающих структуру файла эффекта (рисунок 2.5). Как говорилось в прошлом разделе, в каждом эффекте HLSL имеется несколько техник, которые в свою очередь содержат несколько проходов. При этом минимально возможный эффект включает хотя бы одну технику и один проход. Соответственно, класс Effect содержит коллекцию Techniques с экземплярами классов EffectTechnique, инкапсулирующих техники. В свою очередь, каждая техника содержит коллекцию Passes экземпляров класса EffectPass с информацией об эффекте.

Коллекции класса Effect

Рис. 2.5. Коллекции класса Effect

Загрузка и компиляция файла эффекта

Загрузка эффекта из файла *.fx с последующей компиляцией осуществляется при помощи статического метода Effect.CompileEffectFromFile:

public static CompiledEffect CompileEffectFromFile
(string effectFile, CompilerMacro[] preprocessorDefines, 
CompilerIncludeHandler includeHandler, CompilerOptions 
options, TargetPlatform platform);

где

  • effectFile – имя файла с эффектом.
  • preprocessorDefines – массив макроопределений (аналогов директивы #define в C# ), используемых при компиляции эффекта. Мы будем использовать значение null.
  • includeHandler – объект, используемый для обработки директив #include в fx -файле. Так как наш файл не содержит директив #include, мы будем использовать значение null.
  • options – опции компилятора HLSL, задаваемые с использованием перечислимого типа CompilerOptions (таблица 2.5). Члены типа CompilerOptions являются битовыми флагами, что позволяет комбинировать их с использованием оператора OR. В качестве этого параметра, как правило, передается значение CompilerOptions.None.
  • platform – значение перечислимого типа TargetPlatform, указывающее платформу, для которой компилируется эффект. В XNA Framework 1.0 поддерживаются две платформы: TargetPlatform.Windows и TargetPlatform.Xbox360, названия которых говорят за себя. Все примеры этого курса будут использовать значение TargetPlatform.Windows.
Таблица 2.5. Некоторые члены перечислимого типа CompilerOptions.
Член перечисления Значение
None Нет никаких опций
Debug Вставляет в ассемблерный код отладочную информацию
NotCloneable(*) Запрещает клонирование (создании копии) эффекта при помощи метода Clone. Эта опция уменьшает объем используемой памяти, так как в оперативной памяти не хранится информация, необходимая для клонирования эффекта. При этом экономия оперативной памяти достигает 50%.
ForceVertexShaderSoftwareNoOptimizations Форсирует компиляцию вершинного шейдера с использованием максимально возможной версии Pixel Shader (на момент написания курса это 3.0), не взирая на возможности текущего графического устройства.
ForcePixelShaderSoftwareNoOptimizations Форсирует компиляцию пиксельного шейдера с использованием максимально возможной версии Pixel Shader (на момент написания курса это 3.0), не взирая на возможности текущего графического устройства.
PartialPrecision Использовать минимальную точность вычислений, поддерживаемую текущим графическим устройством. Как правило, при использовании этой опции типы double и float заменяются на half.
SkipOptimization Отключает оптимизацию кода
SkipValidation Отключает проверку соответствия сгенерированного кода возможностям текущего ускорителя (не превышено ли ограничение на максимальную длину программы и т.д.) перед отправкой откомпилированного кода шейдера в драйвер. Этот флаг полезен в тех случаях, когда драйверу все же удается оптимизировать слишком длинный ассемблеро-подобный код таким образом, чтобы уложиться в ограничения архитектуры графического процессора.

(*) – не поддерживается методом CompileEffectFromFile.

Если метод Effect.CompileEffectFromFile не сможет открыть fx -файл (например, из-за его отсутствия), то будет сгенерировано одно из исключений производных от System.IO.IOException вроде System.IO.FileNotFoundException или System.IO.DirectoryNotFoundException.

Метод CompileEffectFromFile возвращает структуру CompiledEffect, содержащую откомпилированный код эффекта, а так же отчет о компиляции эффекта (возникли ли какие-либо проблемы при компиляции эффекта и т.п.).

public struct CompiledEffect
{
// Сообщения о проблемах, возникших при компиляции эффекта
public string ErrorsAndWarnings { get; } 
// Был ли эффект откомпилирован удачно
public bool Success { get; } 
// Если свойство Success равное true, 
содержит откомпилированный код эффекта
public byte[] GetEffectCode();
...
}

Стоит отметить, что метод GetEffectCode возвращает байт-код промежуточного языка наподобие того, что содержится в exe -файлах для платформы .NET. Соответственно, этот код с точки зрения человека является лишь бессмысленным набором байт. Тем не менее, как мы увидим далее, при необходимости этот байт-код может быть легко дизассемблирован удобочитаемый текстовый вид.

Примечание

При желании приложение может сохранить откомпилированный байт-код в каком-нибудь файле, и при следующих запусках считывать из файла уже готовый откомпилированный байт-код. Кстати, Visual C# 2005 Express при компиляции проектов, использующих Content Pipeline, автоматически выполняет компиляцию fx -файлов проекта и сохраняет полученный промежуточный код в файлах с расширением nvb. Таким образом, приложениям, использующим Content Pipeline, нет нужды самостоятельно компилировать fx -файлы.

Следующий этап - компиляция байт-кода промежуточного языка в машинный код вершинных и пиксельных процессоров текущей видеокарты. Эта операция автоматически осуществляется конструктором класса Effect:

public Effect(GraphicsDevice graphicsDevice, 
byte[] effectCode, CompilerOptions options, EffectPool pool);

где

  • graphicsDevice - устройство Direct3D, которое будет использоваться для работы с эффектом
  • byte[] effectCode -код эффекта, предварительно скомпилированный при помощи метода CompileEffectFromFile.
  • options - опции компилятора, задающиеся использованием перечислимого типа CompilerOptions (таблица 2.5.). Довольно часто в качестве этого параметра передается значение CompilerOptions.NotCloneable, что позволяет несколько сэкономить объем используемой оперативной памяти.
  • pool - экземпляр класса EffectPool, позволяющий нескольким эффектам использовать общие параметры. В наших первых примерах мы будем использовать не более одного fx -файла, этот параметр будет равен null.

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

public EffectTechniqueCollection Techniques { get; }

Однако XNA -приложения достаточно редко обращаются к этой коллекции. Дело в том, что конструктор класса Effect автоматически находит первую попавшуюся технику эффекта и присваивает ее свойству CurrentTechnique.

public EffectTechnique CurrentTechnique { get; set; }

Соответственно, если эффект содержит лишь единственную технику, приложению для получения информации об этой техники достаточно обратиться к свойству CurrentTechnique, возвращающему экземпляр класса EffectTechnique, инкапсулирующий технику эффекта. Ниже приведено сокращенное определение класса EffectTechnique:

public sealed class EffectTechnique
{
// Название техники
public string Name { get; } 
// Коллекция проходов техники
public EffectPassCollection Passes { get; }
 // Выполняет валидацию техники
public bool Validate();
...
}

И так, каждый эффект может содержать несколько техник. При этом некоторые техники эффекта могут нормально работать на текущем GPU, а некоторые (наиболее продвинутые) нет. Если требования техники ( technique ) превышают возможности текущего GPU (например, пользователь пытается запустить эффект использующий профиль ps_1_4 на NV2x ), XNA Framework проигнорирует технику. В результате примитивы, использующие эту технику, будут отображаться некорректно. Во избежание подобных неприятностей необходимо заранее проверить возможность выполнения данной техники средствами текущего графического устройства. Для этой цели в классе EffectTechnique предусмотрен метод Validate. Если техника может быть выполнена на текущем устройстве, метод Validate возвращает значение true, иначе - false. Во втором случае, приложение может попытаться подобрать альтернативную технику с меньшими системными требованиями или завершить приложение с сообщением о недостаточной "мощности" текущей видеокарты.

Резюмируя все вышесказанное можно предположить, что код для загрузки эффекта и выбора техники, как правило, имеет следующую структуру:

GraphicsDevice device;
// Флаг, устанавливаемый в значение true при 
аварийном завершении работы приложения из-за
// проблем в обработчике события Load
closing = false;
...
// Этот код обычно размещается в обработчике события Load.
CompiledEffect compiledEffect;
try
{
// Загружаем эффект из файла и компилируем в промежуточный код
compiledEffect = Effect.CompileEffectFromFile
(effectFileName, null, null, 
CompilerOptions.None, TargetPlatform.Windows); 
}
// Если при загрузке файла эффекта возникли проблемы 
catch (IOException ex) 
{ 
// Выводим сообщение об ошибке
MessageBox.Show(ex.Message, "Критическая ошибка", 
MessageBoxButtons.OK, 
 MessageBoxIcon.Error);
// Завершаем работу приложения. Так как метод Close() 
нельзя вызвать из обработчика 
// события Load, приходится идти на хитрости 
(использовать обработчик события 
// Application.Idle, вызывающий внутри себя метод 
Close главной формы приложения, если 
// флаг closing равен true).
closing = true;
Application.Idle += new EventHandler(ApplicationIdle);
return; }
// Если эффект был скомпилирован с ошибками
if (!compiledEffect.Success)
{
// Выдаем сообщение об ошибке
MessageBox.Show(String.Format("Ошибка при компиляции 
эффекта: \r\n{0}",
compiledEffect.ErrorsAndWarnings), "Критическая ошибка",
 MessageBoxButtons.OK,
MessageBoxIcon.Error); 
closing = true;
Application.Idle += new EventHandler(ApplicationIdle); return; }
// Компилируем байт-код промежуточного языка и создаем объект эффекта. 
effect = new Effect(device, compiledEffect.GetEffectCode(), 
CompilerOptions.NotCloneable, null);
// Если текущая техника не может быть выполнена на текущем  графическом устройстве.
if (!effect.CurrentTechnique.Validate())
{
// Выводим сообщение об ошибке и завершаем работу приложения
MessageBox.Show(String.Format("Ошибка при 
валидации техники \"{0}\" эффекта \"{1}\"\n\r" +
"Скорее всего, функциональность шейдера
 превышает возможности GPU",
effect.CurrentTechnique.Name, effectFileName), 
"Критическая ошибка", MessageBoxButtons.OK,
MessageBoxIcon.Error);
closing = true;
Application.Idle += new EventHandler(ApplicationIdle);
return;
}

Визуализация объекта, использующего эффект

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

public void Begin();

Далее приложение должно перебрать все проходы (коллекция passes) текущей техники ( CurrentTechnique ) и для каждой техники:

  1. Вызвать метод Pass текущего эффекта.
  2. Визуализировать примитивы с использованием метода GraphicsDevice .DrawUserPrimitives.
  3. Вызывать метод End текущего эффекта.

По окончанию визуализации эффекта приложение должно вызвать метод Effect.End. В итоге код визуализации примитива выглядит следующим образом:

Effect effect;
...
// Фрагмент типового обработчика события Paint
...
// Начинаем визуализацию примитивов с использованием эффекта effect.
effect.Begin();
// Перебираем все проходы визуализации текущей техники
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
// Начинаем визуализацию текущего прохода
pass.Begin() ; 
// Визуализируем примитивы
device.DrawUserPrimitives(...);
...
device.DrawUserPrimitives(...); 
// Завершаем проход
pass.End(); 
} 
// Заканчиваем визуализацию эффекта
effect.End();

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

Примечание

Как известно, оператор foreach, используемый нами для перебора коллекции проходов ( effectCurrentTechnique.Passes ), обладает несколько более низкой производительностью по сравнению с классическим оператором for. Однако при небольшом количестве итераций эта особенность не является сколь либо заметным недостатком.

Андрей Леонов
Андрей Леонов

Reference = add reference, в висуал студия 2010 не могу найти в вкладке Solution Explorer, Microsoft.Xna.Framework. Его нету.