При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода: 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.Graphics;
namespace Application1 { public partial class MainForm : Form { // Объявим поле графического устройства для видимости в методах GraphicsDevice device;
public MainForm() { InitializeComponent();
// Подпишемся на событие Load формы this.Load += new EventHandler(MainForm_Load);
// Попишемся на событие FormClosed формы this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed); }
void MainForm_FormClosed(object sender, FormClosedEventArgs e) { // Удаляем (освобождаем) устройство device.Dispose(); // На всякий случай присваиваем ссылке на устройство значение null device = null; }
void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства PresentationParameters presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height;
// Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); }
protected override void OnPaint(PaintEventArgs e) { device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
base.OnPaint(e); } } } Выбрасывается исключение: Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл. Делаю все пунктуально. В чем может быть проблема? |
Windows Forms и XNA 3.0
Функции и семантики HLSL
Данные в HLSL упаковываются в функции для выполнения определенных действий, например, можно объявить такую функцию
float4 MainVS(float3 pos) { return float4(pos, 1.0); }
Но она не является полноценным векторным шейдером VS. С точки зрения DirectX это всего лишь простая функция, принимающая в качестве параметра трехмерный вектор и возвращающая четырехмерный вектор. Описание вершины может содержать множество разнородных данных: цвет, геометрические координаты и т.п. Поэтому мы должны указать, какой именно вид данных нужно обрабатывать в функции. В HLSL для этой цели используются так называемые семантики (semantics), предназначенные для указания именно тех данных, которые будут проходить через различные ступени обработки графического конвейера.
Для более сложных данных в названии семантики требуется указывать целочисленный индекс. При отсутствии индекса в названии семантики он полагается равным 0. Вот пример некоторых семантик с индексом n:
Семантика | Описание |
---|---|
POSITION[n] | Координаты вершины |
COLOR[n] | Цвет вершины |
PSIZE[n] | Размер точки (при визуализации набора точек) |
Уточняющая семантика пишется через двоеточие после объявления обрабатываемого данного: для входного параметра функции - после его объявления в списке параметров, для выходного - после заголовка функции. Таким образом, в нашем примере для связи входного (выходного) параметра pos функции MainVS() с координатами вершины необходимо использовать семантику POSITION:
float4 MainVS(float3 pos : POSITION) : POSITION { return float4(pos, 1.0); }
Вот теперь мы наконец-то получили полноценный вершинный шейдер VS. Следующий этап - написание пиксельного шейдера PS. Наш пиксельный шейдер будет просто закрашивать все пикселы цветом морской волны (Aqua = {0, 255, 255} ). Но в HLSL для определения цветов приняты вещественные значения и яркость соответствующего цвета модели RGB задается в диапазоне (0.0, 1.0). Поэтому для цвета Aqua и непрозрачного альфа-канала окончательный код пиксельного шейдера будет таким
float4 MainPS() : COLOR { return float4(0.0, 1.0, 1.0, 1.0); // RGB hex=(0x00FFFF) и непрозрачный альфа-канал 0xFF }
Техники, проходы и профили HLSL
Мы получили функции для вершинного и пиксельного процессора - вершинный и пиксельный шейдеры. Заключительный этап написания эффекта - создание техники (technique), использующей эти шейдеры. Ниже приведено определение техники с названием Fill, использующей вершинный шейдер MainVS() и пиксельный шейдер MainPS():
technique Fill { pass p0 { VertexShader = compile vs_1_1 MainVS(); PixelShader = compile ps_1_1 MainPS(); } }
Как видно, техника определяется с использованием ключевого слова technique. Каждая техника содержит один или несколько проходов, объявляемых с использованием ключевого слова pass. В свою очередь, каждому проходу ставится в соответствие пиксельный и вершинный шейдер. Наша техника Fill содержит единственный проход с названием p0, внутри которого используется синтаксис:
VertexShader = compile {используемый профиль} {вершинный шейдер}; PixelShader = compile {используемый профиль} {пиксельный шейдер};
Профиль шейдера (shader profile) определяет версию языка HLSL, на котором будет скомпилирован шейдер. Профиль учитывает архитектурные особенности целевого графического процессора при генерации промежуточного ассемблерного кода. В большинстве случаев каждой версии HLSL соответствует один профиль. Например, языку Vertex Shader 1.1 соответствует профиль vs_1_1, Pixel Shader 1.4 – профиль ps_1_4, Pixel Shader 2.0 – профиль ps_2_0, и так далее. Однако некоторым языкам, вроде Pixel Shader 2.x, соответствует два профиля: в данном случае это ps_2_a и ps_2_b, при этом первый профиль генерирует код Pixel Shader 2.x, оптимизированный под архитектуру графического процессора NV3x, а второй – под R4xx. Ниже приведено соответствие между профилями и версиями HLSL.
Профиль | Версия вершинных шейдеров |
---|---|
vs_1_0 | 1.0 |
vs_1_1 | 1.1 |
vs_2_0 | 2.0 |
vs_2_a | 2.x |
vs_3_0 |
3.0 |
Профиль | Версия пиксельных шейдеров |
ps_1_0 | 1.0 |
ps_1_1 | 1.1 |
ps_1_2 | 1.2 |
ps_1_3 | 1.3 |
ps_1_4 | 1.4 |
ps_2_0 | 2.0 |
ps_2_a | 2.x (оптимизация для NV3x) |
ps_2_b | 2.x (оптимизация для R4xx) |
ps_3_0 |
3.0 |
Большинство видеокарт поддерживает несколько профилей вершинных и пиксельных шейдеров. В результате каждый разработчик сталкивается с проблемой выбора используемого профиля. Чаще всего выбор версии шейдеров определяется минимальными требованиями к приложению.
Допустим, необходимо, чтобы наша программа могла работать на видеокартах класса ATI Radeon 9500 (R3xx) и выше, NVIDIA GeForce FX 5200 (NV3x) и выше, а так же Intel GMA 900 и выше. Все эти видеокарты поддерживают профили вершинных шейдеров vs_1_0, vs_1_1, vs_2_0 и профили пиксельные шейдеров ps_1_0, ps_1_1, ps_1_2, ps_1_3, ps_1_4 и ps_2_0. Таким образом, можно смело использовать профили vs_2_0 и ps_2_0 для всех шейдеров. При этом для некоторых эффектов можно предусмотреть дополнительные техники (technique) для видеокарт класса High End, использующих профили vs_3_0 и ps_3_0.
Если мы хотим поступиться эффективностью в пользу масштабируемости приложения, следует использовать минимальную версию профилей, необходимую для нормальной компиляции шейдеров, например, профили vs_1_1 и ps_1_1. Это позволит работать нашему приложению даже на стареньких видеокартах семейства GeForce3 (NV20).
Добавление эффекта в приложение
Пришла пора создать эффект для нашего упражнения - файл с расширением .fx, содержащий инструкции для графических ускорителей видеокарты. Эффект будет ориентирован на минимальную версию профилей.
- В панели Solution Explorer вызовите контекстное меню для узла Application2 и командой Add/New Folder создайте папку с именем Data
- В панели Solution Explorer вызовите контекстное меню для папки Data и командой Add/New Item добавьте в нее текстовый файл с именем ColorFill.fx
- В панели Solution Explorer выделите файл ColorFill.fx и в панели Properties установите для него свойство Copy to Output Directory в значение Copy if newer (копировать в каталог сборки свежую версию файла)
- Заполните файл с эффектом следующим кодом
struct VertexInput { float4 pos : POSITION; float4 color : COLOR; }; struct VertexOutput { float4 pos : POSITION; float4 color : COLOR; }; VertexOutput MainVS(VertexInput input) { return input; } float4 MainPS(VertexOutput input):COLOR { return input.color; } technique Fill { pass p0 { VertexShader = compile vs_1_1 MainVS(); PixelShader = compile ps_1_1 MainPS(); } }
Для использования созданного эффекта его нужно загрузить из файла эффекта в приложение и откомпилировать в байт-код. Для этого применяется одна из перегрузок статического метода класса Effect, в том числе
public static Microsoft.Xna.Framework.Graphics.CompiledEffect CompileEffectFromFile( string effectFile, Microsoft.Xna.Framework.Graphics.CompilerMacro[] preprocessorDefines, Microsoft.Xna.Framework.Graphics.CompilerIncludeHandler includeHandler, Microsoft.Xna.Framework.Graphics.CompilerOptions options, Microsoft.Xna.Framework.TargetPlatform platform )
- effectFile – имя загружаемого файла с эффектом
- preprocessorDefines – массив макроопределений (аналогов директивы #define ), используемых при компиляции эффекта. Мы будем использовать значение null
- includeHandler – объект, используемый для обработки директив #include в fx-файле. Так как наш файл не содержит директив #include, мы будем использовать значение null
- options – опции компилятора HLSL, задаваемые с использованием перечислимого типа CompilerOptions. Члены типа CompilerOptions являются битовыми флагами, что позволяет комбинировать их с использованием побитовой операции OR. В качестве этого параметра, как правило, передается значение CompilerOptions.None
- platform – значение перечислимого типа TargetPlatform, указывающее платформу, для которой компилируется эффект. Мы будем использовать значение TargetPlatform.Windows
Результат компиляции, возвращаемый методом CompileEffectFromFile(), нужно сохранить в экземпляре compiledEffect структуры типа CompiledEffect. Если компиляция прошла успешно, то булево свойство compiledEffect.Success принимает значение true. Если возникли проблемы с открытием fx -файла (например, файл не найден), то будет сгенерировано одно из исключений, производных от System.IO.IOException (например, System.IO.FileNotFoundException или System.IO.DirectoryNotFoundException ).
В дальнейшем объект compiledEffect нам понадобится, чтобы передать с помощью вызова метода compiledEffect.GetEffectCode() скомпилированный байт-код в одну из перегрузок конструктора класса Effect при создании объекта эффекта
public Effect( Microsoft.Xna.Framework.Graphics.GraphicsDevice graphicsDevice, byte[] effectCode, Microsoft.Xna.Framework.Graphics.CompilerOptions options, Microsoft.Xna.Framework.Graphics.EffectPool pool)
- graphicsDevice - устройство Direct3D, которое будет использоваться для работы с эффектом
- effectCode - код эффекта, предварительно скомпилированный при помощи метода CompileEffectFromFile()
- options - опции компилятора, определяемые перечислимым типом CompilerOptions. Довольно часто в качестве этого параметра передается значение CompilerOptions.NotCloneable. Оно запрещает клонирование (создание копии) эффекта при помощи метод Clone(). Это уменьшает объем используемой памяти, так как в оперативной памяти видеокарты не хранится информация, необходимая для клонирования эффекта. При этом экономия оперативной памяти достигает 50%
- pool - экземпляр класса EffectPool, позволяющий нескольким эффектам использовать общие параметры. Мы используем один fx-файл и этот параметр будет равен null
Имена всех техник, указанные в fx -файле, при создании объекта effect, помещаются в его свойство-коллекцию Techniques типа EffectTechniqueCollection. Коллекция содержит объекты техник effectTechnique типа EffectTechnique. Перебрав все объекты этой коллекции можно проверить с помощью метода CurrentTechnique.Validate(), поддерживаются ли указанные техники видеокартой текущего компьютера.
Если эффект содержит лишь единственную технику, как в нашем случае, то задача упрощается. Дело в том, что конструктор класса Effect при создании экземпляра эффекта автоматически находит первую попавшуюся технику и присваивает ее свойству CurrentTechnique. Поэтому приложению для получения информации об этой технике достаточно обратиться к свойству CurrentTechnique и с помощью метода CurrentTechnique.Validate() проверить возвращаемый им булев флаг, поддерживает GPU видеокарты данную технику или нет.
- В панели Solution Explorer вызовите контекстное меню для узла References проекта Application2 и командой Add Reference добавьте через одноименное окно из вкладки Recent окна (недавно выбранные) библиотеку Microsoft.Xna.Framework.dll
- Через контекстное меню переведите форму в режим редактирования View Code и добавьте в файл MainForm.cs следующий код
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 XNAGraphics = Microsoft.Xna.Framework.Graphics; using System.IO; namespace Application2 { public partial class MainForm : Form { // Вспомогательные поля const string effectFileName = "Data\\ColorFill.fx"; GraphicsDevice device = null; PresentationParameters presentParams = new PresentationParameters(); Effect effect = null; VertexDeclaration decl = null; VertexPositionColor[] vertices = new VertexPositionColor[3]; bool closing = false; public MainForm() { InitializeComponent(); } } }
- Добавьте в класс MainForm метод с именем CreateDeviceAndEffect() и заполните его следующим кодом
void CreateDeviceAndEffect() { // Отключить у панели автоматическую очистку фона и задать // автоматический вызов события Paint при изменении размеров xnaPanel1.SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области панели presentParams.BackBufferWidth = xnaPanel1.ClientSize.Width; presentParams.BackBufferHeight = xnaPanel1.ClientSize.Height; // Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, xnaPanel1.Handle, presentParams); // Создаем декларацию о формате применяемого хранилища вершин decl = new VertexDeclaration(device, VertexPositionColor.VertexElements); // Загружаем и компилируем эффект из файла CompiledEffect compiledEffect; try { compiledEffect = Effect.CompileEffectFromFile(effectFileName, null, null, CompilerOptions.None, TargetPlatform.Windows); } catch (IOException ex) { closing = true; MessageBox.Show(ex.Message, "Ошибка при загрузке эффекта", MessageBoxButtons.OK, MessageBoxIcon.Error); Application.Idle += new EventHandler(Application_Idle); return; } // Проверяем, нормально ли откомпилирован эффект if (!compiledEffect.Success) { closing = true; MessageBox.Show(String.Format( "Ошибка при компиляции эффекта:\n{0}", compiledEffect.ErrorsAndWarnings), "Критическая ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); Application.Idle += new EventHandler(Application_Idle); return; } // Создаем объект эффекта effect = new Effect(device, compiledEffect.GetEffectCode(), CompilerOptions.NotCloneable, null); // Проверяем, поддерживается ли видеокартой указанная в эффекте техника if (!effect.CurrentTechnique.Validate()) { closing = true; MessageBox.Show(String.Format("Ошибка при проверке техники \"{0}\" эффекта \"{1}\"\n" + "Скорее всего, функциональность шейдера превышает возможности GPU", effect.CurrentTechnique.Name, effectFileName), "Критическая ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); Application.Idle += new EventHandler(Application_Idle); return; } } void Application_Idle(object sender, EventArgs e) { this.Close(); }
- Поместите вызов метода CreateDeviceAndEffect() в конструктор класса после кода инициализации компонентов формы
public MainForm() { InitializeComponent(); // Создание устройства и эффекта CreateDeviceAndEffect(); }
Теперь создадим обработчики нужных событий и заполним их кодом.