Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Работа выполнена на основе материалов С.А. Гайдукова, приведенных в источнике http://www.intuit.ru/department/se/intxna
Создайте новое решение
и новый проект командой File/New/Project, заполнив окно мастера так
Переименуйте файл формы Form1.cs в MainForm.cs
Выделите форму в конструкторе
форм и в панели Properties в свойстве Text установите заголовок
окна " Упражнение 1 "
Командой Project/Add Reference вызовите
окно добавления ссылок и на вкладке .NET выберите библиотечную
сборку Microsoft.Xna.Framework.dll версии 3.0
В этой библиотеке находится множество компонентов 3D -графики, размещенные
в нескольких пространствах имен, в том числе в интересующем нас пространстве
имен Microsoft.Xna.Framework.Graphics

Прежде всего воспользуемся классом GraphicsDevice, инкапсулирующим
средства управления графическим устройством - видеокартой компьютера. Конструктор
этого класса описывается так
public GraphicsDevice(Microsoft.Xna.Framework.Graphics.GraphicsAdapter adapter, Microsoft.Xna.Framework.Graphics.DeviceType deviceType, System.IntPtr renderWindowHandle, Microsoft.Xna.Framework.Graphics.PresentationParameters presentationParameters)
GraphicsAdapter,
соответствующее выбранной для работы видеокарте. Современные компьютеры содержат
несколько видеокарт, поэтому проще всего выбрать свойства GraphicsAdapter.DefaultAdapter DeviceType,
которое имеет значенияPanel ), который будет использоваться для вывода графических
изображенийPresentationParameters,
свойства которого определяют настройки представления изображения на экранеДля создания и настройки графического устройства нужно объявить в классе MainForm поле,
зарегистрировать для события Load формы обработчик, в котором создать
экземпляр устройства и настроить его с помощью свойств соответствующих объектов.
Вызовите контекстное
меню формы MainForm, щелкнув на ней правой кнопкой мыши, и выполните команду View Code
Подключите иснструкцией using к файлу MainForm.cs пространство имен Microsoft.Xna.Framework.Graphics
Объявите в классе MainForm поле типа GraphicsDevice с именем deviceusing 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);
}
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);
}
}
}
Прежде, чем создать объект графического устройства, мы создаем вспомогательный
объект presentParams, через свойства которого устанавливаем настройки
для будущего устройства. Размеры заднего буфера можно не задавать, поскольку
они по умолчанию в объекте presentParams установлены по умолчанию в
значение 0, а это значит, что устройство device само
вычислит их. Передним буфером считается экран, а задний буфер добавляется для
устранения мерцания, в итоге при добавлении одного заднего буфера обеспечивается
режим двойной буферизации.
Таким образом, в приведенном коде мы создали графическое устройство со следующими характеристиками:
Теперь нужно прописать код очистки графических буферов перед рисованием нового
изображения. Для этого существует метод Clear() графического устройства,
имеющий несколько перегрузок. Пока воспользуемся упрощенной перегрузкой этого
метода,
которая очищает только экранный буфер заданным цветом:
public void Clear(Microsoft.Xna.Framework.Graphics.Color color)
Куда же вставить вызов функции очистки графического устройства? Когда происходит
разрушение экрана за счет сворачивания окна или заслонения другими окнами,
то при необходимости его восстановления операционная система генерирует событие Paint унаследованного формой класса Control и мы можем либо подписать
обработчик на это событие и туда вставить очистку, либо перекрыть виртуальный
метод диспетчеризации OnPaint() формы
и в него вставить вызов очистки. Воспользуемся вторым вариантом...
В классе MainForm как
член класса наберите ключевое слово override, после введения
символа пробела в открывшемся окне подсказчика кода IntalliSense выберите
метод диспетчеризации OnPaint() и
завершите набор нажатием клавиши Enter
В перекрытом методе
диспетчеризации OnPaint() введите код вызова метода очистки устройства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);
}
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);
}
}
}
Обратите внимание, что мы были вынуждены прописать полное имя структуры Color во избежании конфликта имен, поскольку точно такая же структура находится в
подключенном к файлу пространстве имен System.Drawing.
При завершении работы нашего приложения
сборщик мусора GC среды исполнения CLR попытается автоматически освободить
графическое устройство. Но сделать это корректно ему не удастся. Дело в том,
классы XNA являются
тонкой оболочкой API DirectX, а сам DirectX освобождает только те объекты,
которые были созданы в его потоке. Сборщик мусора же для своей работы создает
новый отдельный поток, который не сможет корректно удалить объекты DirectX,
в том числе созданный нами объект device. В связи с этим нам нужно самим
позаботиться об удалении таких критичных объектов в нашем приложении.
В конструкторе MainForm() одноименного класса
подпишитесь на обработчик события FormClosed и заполните обработчик кодом
удаления устройства deviceusing 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);
}
}
}
Запустите приложение
на выполнениеПока ничего не произошло и мы не видим графического окна в клиентской области
формы, хотя событие Paint исправно срабатывает, в чем можно убедиться,
если установить точку останова (Breakpoint) в перекрытом методе диспетчеризации OnPaint().

Всe дело в том, что мы используем двойную буферизацию, то есть наше приложение
выполняет все графические построения в заднем невидимом вспомогательном буфере.
После окончания визуализации необходимо скопировать информацию из этого вспомогательного
буфера на форму. Эту операцию выполняет метод Present() класса GraphicsDevice.
Добавьте вызов
метода Present() в перекрытый метод диспетчеризации OnPaint()protected override void OnPaint(PaintEventArgs e)
{
device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
Снова запустите
программу на выполнение - на экране появится окно, клиентская область которого
закрашена синим цветом, что нам и требовалось
Теперь попробуем применить более сложную перегрузку метода Clear() для
избирательной очистки экрана и нарисовать с ее помощью шахматную доску:
public void Clear(Microsoft.Xna.Framework.Graphics.ClearOptions options, Microsoft.Xna.Framework.Graphics.Color color, float depth, int stencil, Microsoft.Xna.Framework.Rectangle[] regions)
Target перечисления ClearOptions.
Остальные флаги DepthBuffer и Stencil этого перечисления используются
для очистки соответственно буфера глубины и буфера шаблона, и будут рассмотрены
далееMicrosoft.Xna.Framework.Graphics.Color, являющейся аналогом структуры System.Drawing.Color. Необходимость дублирования обусловлено стремлением
разработчиков Microsoft сделать XNA Framework переносимой на другие платформыMicrosoft.Xna.Framework.Rectangle,
задающих прямоугольные области экрана, которые должны быть очищены. Области
экрана задаются в оконных координатах клиентской области формы - начало координат
расположено в ее левом верхнем углуДля рисования шахматной доски вначале мы очистим экран белым цветом, затем создим массив областей экрана, соответствующих клеткам доски коричневого цвета и ещe раз очистим экран, но уже коричневым цветом.
Модифицируйте перекрытый
метод OnPaint() следующим образомprotected override void OnPaint(PaintEventArgs e)
{
// Очищаем экран белым цветом
device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke);
// Создаeм массив областей закраски, соответствующих коричневым клеткам
Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32];
int k = 0; // Счетчик элементов массива
// Перебираем коричневые клетки шахматной доски
for (int j = 0; j < 8; j++) // Строки шахматной доски
for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски
{
// Заносим в массив параметры рисования очередной клетки
rects[k] = new Microsoft.Xna.Framework.Rectangle(
i * this.ClientSize.Width / 8, // Отступ по горизонтали
j * this.ClientSize.Height / 8, // Отступ по вертикали
this.ClientSize.Width / 8, // Ширина клетки
this.ClientSize.Height / 8); // Высота клетки
k++; // Увеличиваем счетчик
}
// Закрашиваем все области из массива rects коричневым цветом
device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
Graphics.Color.Brown, 0.0f, 0, rects);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
Запустите приложение
- на экране появится окно, раскрашенное как шахматная доска
Попробуйте поизменять размеры окна и отметьте, что программа отображает доску непонятным образом как при уменьшении, так и при увеличении окна. Устраним сначала проблему, возникающую при уменьшении окна.
Начнем с того, что
при уменьшении окна необходимости в дорисовке экрана нет и событие Paint наследуемого
формой класса Control не срабатывает. В принципе, для борьбы с этим
недоразумением мы могли бы перекрыть
метод диспетчеризации OnResize() формы (или подписаться
на событие Resize )
и вставить туда вызов метода Invalidate(), принудительно генерирующего
событие Paint. Однако существует гораздо более элегантное решение:
если установить у формы стиль ResizeRedraw, то при изменении размера
формы будет автоматически генерироваться событие Paint.
Добавьте
в обработчик события Load установку стиля формы ResizeRedrawvoid 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
Запустите приложение
и поизменяйте размеры окнаТеперь экран при уменьшении размеров стал перерисовываться. Но здесь стала очевидной другая проблема: при изменении размеров появились заметные мигания окна. Чтобы эти мигания стали еще заметнее, выполните следующее:
В рабочей области редактора
кода вызовите контекстное меню и выполните команду View Designer (или
просто выполните команду меню оболочки View/Designer )
Выделите форму щелчком
мыши и в панели Properties установите свойство BackColor формы в значение Green
Запустите приложение
и поизменяйте размеры окна - сейчас мигания зеленого цвета на фоне шахматной
доски стали более заметныДело здесь в том, что перед каждым вызовом события Paint автоматически
вызывается виртуальный метод OnPaintBackground() наследуемого формой
класса Control, который
очищает экран цветом BackColor. Эта функциональность позволяет разработчику,
использующему обычный GDI+, не заботится об очистке экрана, однако в
нашем случае такая "самовольная" очистка
формы стандартными средствами Windows приводит лишь к мерцанию. Следовательно,
нам необходимо запретить форме закрашивать экран перед вызовом обработчика
события Paint.
Это делается установкой для формы стиля Opaque.
Добавьте в обработчик
события Load установку стиля формы Opaquevoid 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Запретить очистку экрана средствами GDI+
this.SetStyle(ControlStyles.Opaque, true);
}
Запустите приложение
- теперь мигания формы при изменении ее размеров исчезлиИ так, первую проблему мы решили, но осталась вторая, гораздо более неприятная
- некорректное масштабирование шахматной доски при изменении размера окна.
Дело в том, что при создании графического устройства мы задаeм размер вспомогательного
буфера, используемого при двойной буферизации, равным размеру клиентской области
окна и к тому же только один раз (в обработчике события Load формы).
Когда мы изменяем размер окна, его клиентская область так же изменяется. А
вот размер вспомогательного буфера графического устройства остаeтся неизменным.
Получается, что при изменении размеров окна происходит рассинхронизация между
размерами клиентской области окна просмотра и вспомогательного буфера-источника,
в котором рисуется изображение. В результате, приложение начинает
работать некорректно.
Чтобы синхронно подстраивать параметры графического устройства под новые размеры
окна, нужно перекрыть метод диспетчеризации OnResize() формы, включить в него новые
настройки объекта presentParams и перезапустить объект device его
методом Reset(). Но прежде всего нужно ссылку на объект presentParams сделать
видимой в методе диспетчеризации OnResize(), т.е. вынести ее объявление в поле класса.
На основе вышесказанного
внесите следующие изменения в класс MainFormusing 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;
// Объявим объект настроек графического устройства
PresentationParameters presentParams;
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 = 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Запретить очистку экрана средствами GDI+
this.SetStyle(ControlStyles.Opaque, true);
}
protected override void OnResize(EventArgs e)
{
// Устанавливаем размеры заднего буфера по клиентской области окна формы
presentParams.BackBufferWidth = this.ClientSize.Width;
presentParams.BackBufferHeight = this.ClientSize.Height;
// Сбрасываем устройство и применяем к нему новые параметры
device.Reset(presentParams);
base.OnResize(e);
}
protected override void OnPaint(PaintEventArgs e)
{
// Очищаем экран белым цветом
device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke);
// Создаeм массив областей закраски, соответствующих коричневым клеткам
Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32];
int k = 0; // Счетчик элементов массива
// Перебираем коричневые клетки шахматной доски
for (int j = 0; j < 8; j++) // Строки шахматной доски
for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски
{
// Заносим в массив параметры рисования очередной клетки
rects[k] = new Microsoft.Xna.Framework.Rectangle(
i * this.ClientSize.Width / 8, // Отступ по горизонтали
j * this.ClientSize.Height / 8, // Отступ по вертикали
this.ClientSize.Width / 8, // Ширина клетки
this.ClientSize.Height / 8); // Высота клетки
k++; // Увеличиваем счетчик
}
// Закрашиваем все области из массива rects коричневым цветом
device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
Graphics.Color.Brown, 0.0f, 0, rects);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
}
}
Операция сброса устройства, выполняемая при вызове метода Reset(),
является очень медленной операция. Никогда не вставляйте еe без причины в обработчик
события Paint или перекрытый метод диспетчеризации OnPaint(). Они
вызываются очень часто и это приведeт к заметному падению производительности.
В нашем случае эта операция вставлена в перекрытый метод диспетчеризации OnResize() и
выполняется только при изменении пользователем размеров формы, что бывает редко.
Выполните приложение
- теперь рисование при изменениях окна работает нормально
Но здесь скрыта еще одна проблема. Если уменьшить вертикальный размер клиентской
области формы до нуля (уменьшить горизонтальный размер до нуля мешают системные
кнопки и заголовок окна), то приложение завершится аварийно именно при попытке
установки нулевых размеров заднего буфера в графическом устройстве. Для решения
этой проблемы нужно ограничить минимальный размер клиентской области хотя бы
одним пикселом. Это легко можно сделать при помощи свойства MinimumSize формы,
которое задает минимальные размеры окна (но не клиентской области!).
Properties будет не точным. Размер
клиентской области, который отведет система при заданном размере окна в целом,
зависит от множества факторов: установленной операционной
системы, пользовательских настроек и т.п. Правилнее устанавливать значение
свойства MinimumSize программно
посредством метода формы SizeFromClientSize(),
который возвращает размер окна при заданном размере именно клиентской области.
Добавьте в обработчик
события Load следующий код, устанавливающий минимально допустимый
для изменения размер окна при заданном размере клиентской области 1x1 пикселовvoid MainForm_Load(object sender, EventArgs e)
{
// Создаем объект представления для настройки графического устройства
//PresentationParameters presentParams = new 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Запретить очистку экрана средствами GDI+
this.SetStyle(ControlStyles.Opaque, true);
// Вычисляем размер окна при размерах клиентской области 1x1 пикселов.
// Полученное значение присваиваем свойству MinimumSize
this.MinimumSize = this.SizeFromClientSize(new Size(1, 1));
}
Можно запустить приложение и убедиться, что при интерактивном уменьшении размеров клиентской области остается еще один пиксел в запасе и приложение продолжает устойчиво работать. Но есть еще одна скрытая родственная лазейка для получения нулевых размеров клиентской области - минимизация окна с помощью системной кнопки. Для ее решения сброс графического устройства для установки новых размеров буфера нужно выполнять только после проверки состояния окна, убедившись, что оно не минимизировано.
Добавьте в перекрытый
метод диспетчеризации OnResize() следующую проверку состояния окнаprotected override void OnResize(EventArgs e)
{
// Устанавливаем размеры заднего буфера по клиентской области окна формы
presentParams.BackBufferWidth = this.ClientSize.Width;
presentParams.BackBufferHeight = this.ClientSize.Height;
// Сбрасываем устройство и применяем к нему новые параметры
// Если окно не минимизировано
if (this.WindowState != FormWindowState.Minimized)
device.Reset(presentParams);
base.OnResize(e);
}
Запустите приложение,
минимизируйте окно - приложение продолжает устойчиво работать!Операционная система Windows является многозадачной системой, в которой одновременно могут выполняться несколько задач и делить между собой одни и те же ресурсы. Существует опасность, что настройки графического устройства, установленные нашим приложением XNA, в любой момент могут сбиться другим подобным приложением. Такая ситуация называется потерей устройства (Device Lost). Это легко проверить, если при запущенном приложении XNA попытаться изменить разрешение экрана - приложение завершится аварийно.
Отсюда возникает задача о восстановлении потерянной информации, рассмотрением
которой мы сейчас и займемся. Информацию о состоянии, в котором в данный
момент находится устройство, можно получить из свойства GraphicsDeviceStatus нашего
объекта (экземпляра класса GraphicsDevice ), которое имеет одно из значений
одноименного перечисления GraphicsDeviceStatus:
Normal - графическое устройство работает нормальноNotReset - графическое устройство потеряно, но может быть восстановлено
методом Reset() объекта устройстваLost - графическое устройство потеряно и пока не может быть восстановленоПри восстановимой потере устройства приложение выдает исключение Microsoft.Xna.Framework.Graphics.DeviceNotResetException,
а при невосстановимой - Microsoft.Xna.Framework.Graphics.DeviceLostException.
Если сгенерировано исключение DeviceNotResetException, то приложение
должно восстановить устройство методом device.Reset() и снова перерисовать
изображение путeм вызова метода Invalidate(), в противном случае просто
не реагировать на событие Paint, требующее обновить экран.
На основе вышесказанного
модифицируйте обработку события Paint следующим образомbool closing = false;
protected override void OnPaint(PaintEventArgs e)
{
if (closing) return;
try // Попытка
{
// Очищаем экран белым цветом
device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke);
// Создаeм массив областей закраски, соответствующих коричневым клеткам
Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32];
int k = 0; // Счетчик элементов массива
// Перебираем коричневые клетки шахматной доски
for (int j = 0; j < 8; j++) // Строки шахматной доски
for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски
{
// Заносим в массив параметры рисования очередной клетки
rects[k] = new Microsoft.Xna.Framework.Rectangle(
i * this.ClientSize.Width / 8, // Отступ по горизонтали
j * this.ClientSize.Height / 8, // Отступ по вертикали
this.ClientSize.Width / 8, // Ширина клетки
this.ClientSize.Height / 8); // Высота клетки
k++; // Увеличиваем счетчик
}
// Закрашиваем все области из массива rects коричневым цветом
device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
Graphics.Color.Brown, 0.0f, 0, rects);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
// Откаты
catch (DeviceNotResetException) // Устройство можно восстановить
{
device.Reset(presentParams);
this.Invalidate();
}
catch (DeviceLostException) // Устройство нельзя восстановить
{
closing = true; // Больше не выполнять OnPaint()
string title = "Сбой графического устройства";
string message = "Работа программы будет завершена.\n"
+ "Закройте все ненужные программы\n"
+ "и повторите запуск этого приложения";
MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Stop);
Application.Idle += new EventHandler(Application_Idle);
return;
}
catch (Exception ext) // Все другие исключения
{
closing = true;
MessageBox.Show(ext.Message);
Application.Idle += Application_Idle1;
}
}
void Application_Idle(object sender, EventArgs e)
{
this.Close(); // Закрывает окно, у нас оно же и главное окно приложения
}
void Application_Idle1(object sender, EventArgs e)
{
Application.Exit(); // Завершает работу приложения (приведено для разнообразия)
}
Запустите приложение
и попробуйте изменить разрешение экрана, чтобы поймать исключение невосстановимой
потери устройстваВ этом случае будет выдано окно с сообщением

В упражнении 1 наше приложение осуществляло монопольный вывод графической
информации на всю поверхность формы. Такой подход не позволяет размещать на
форме элементы управления.
Можно попробовать использовать вспомогательные диалоговые
окна или плавающие панели инструментов, но это не очень красивое решение проблемы.
Гораздо интереснее было бы научиться выводить информацию не на саму форму,
а на элементы управления, размещенные на ней (к примеру, на элемент управления Panel ). Это позволило бы на освободившемся пространстве размещать другие
нужные элементы пользовательского интерфейса.
Все элементы управления наследуют от класса Control, в том числе класс Form и Panel.
Ранее при управлении экземпляром класса Form мы фактически
использовали методы и свойства класса Control. В данном упражнении мы
с таким же успехом можем управлять экземпляром класса Panel, помещенным
на форму.
Единственная сложность состоит в том, что при управлении экземпляром Form наш код размещался внутри его расширения - классе MainForm, и имел доступ ко всем общедоступным ( public ) и защищенным
( protected ) свойствам и методам, унаследованным, в конечном итоге, от класса Control. А если попытаться просто управлять экземпляром класса Panel,
помещенным на форму, то видимыми из расширения класса Form в объекте Panel (и,
соответственно, в Control ) будут только общедоступные члены. Чтобы
получить полный доступ к нужным сервисам класса Control, нужно создать
расширение класса Panel и упаковать в нем вызовы защищенных членов
в общедоступные.
Фактически, нам нужно создать новый элемент управления, которые называют пользовательскими (в отличии от стандартных библиотечных).
Закройте в рабочей
области оболочки все редактируемые ранее документы командой меню Window/Close All Documents
Командой File/Add/New Project вызовите
мастер добавления в решение нового проекта с именем XNAPanel и
заполните его окно так
Через панель Solution
Explorer переименуйте автоматически созданный файл Class1.cs на XNAPanel.cs
Командой Project/Add Reference подключите
к проекту библиотечные сборки System.Windows.Forms.dll и System.Drawing.dll,
а в начало файла XNAPanel.cs вставьте инструкции:using System.Windows.Forms; using System.Drawing;
Модифицируйте XNAPanel.cs следующим образомusing System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace XNAPanel
{
public class XNAPanel : Panel
{
// Конструктор по умолчанию
public XNAPanel()
{
// Изменим цвет фона панели, чтобы визуально отличить от других
BackColor = Color.CornflowerBlue;
// Установим минимальный размер,
// чтобы устранить возможное исключение
MinimumSize = new Size(1, 1);
}
// Сделаем общедоступным нужный нам защищенный метод
public new void SetStyle(ControlStyles flag, bool value)
{
base.SetStyle(flag, value);
}
}
}
Командой меню Build/Build XNAPanel откомпилируйте
данный проектМы создали библиотечный пользовательский элемент управления для вывода графической информации XNA, который теперь можно размещать на форме.
Командой File/Add/New Project вызовите
мастер добавления в решение нового проекта с именем Application2 и
заполните его окно так
После того, как мастер создаст новый проект и выведет визуальное представление
формы в рабочую область, можно убедиться, что в панели инструментов Toolbox появилась
новая вкладка XNAPanel Components с пиктограммой компонета XNAPanel.
Через Обозреватель
решений переименуйте файл Form1.cs проекта Application2 в MainForm.cs,
поменяйте заголовок окна Text="Упражнение 2" и из панели инструментов Toolbox поместите
на форму экземпляр компонента SplitContainer (можно
просто выполнить двойной щелчок мышью на компоненте). Убедитесь, что свойство Dock объекта splitContainer1 имеет значение Fill, т.е.
что объект занимает всю клиентскую область формы
Установите для объекта splitContainer1 свойство BorderStyle в значение FixedSingle, чтобы создать черные рамки вокруг панелей
элемента
Выделите курсором левую
панель Panel1 объекта splitContainer1, поместите в нее из панели Toolbox экземпляр
нашего компонента XNAPanel и установите его свойство Dock в значение Fill,
чтобы он занял всю область объекта Panel1
Выделите курсором
правую панель объекта splitContainer1, поместите на нее экземпляр
компонента GroupBox и разверните его на всю панель установкой свойства Dock в
значение Fill. Присвойте объекту groupBox1 заголовок " Параметры "
через его свойство Text
Выделите объект groupBox1 и
добавьте в него три текстовых метки Label с надписями
Поместите напротив
этих меток три экземпляра компонента Button и присвойте им имена vertex1Color, vertex2 Color и vertex3Color
Выделите все три экземпляра
компонента Button, очистите для них содержимое свойства Text и
установите свойство Size равным ( 30 ; 20 )
Настройте
для каждой кнопки уникальный цвет фона через свойство BackColor
Добавьте к форме экземпляр
невизуального компонента ColorDialog, пиктограмма которого будет размещена
в подвале рабочей области ( Workspace ) дизайнера формыВ результате форма в режиме проектирования примет следующий вид

Средствами XNA мы нарисуем примитивный треугольник на экземпляре нашего
компонента XNAPanel, цвет вершин которого мы будем выбирать из диалоговой панели.
Для этого, как и ранее, нужно добавить в класс формы несколько
вспомогательных полей, создать обработчики событий Load, FormClosed, Paint и т.д. и заполнить их соответствующим кодом. Но прежде, чем приступить
к программированию обработчиков, познакомимся с новой техникой, которую мы
будем использовать в этом упражнении для управления
видеокартой на низком уровне.
Примитивом называется графический объект - точка, прямая линия, треугольник,
которые используются для построения более сложных объектов. В XNA Framework
поддерживает шесть типов примитивов, заданных структурой PrimitiveType:
список точек PointList, список линий LineList, полоса линий LineStrip,
список треугольников TriangleList, полоса треугольников TriangleStrip и
веер треугольников TriangleFan.
Примитивы описываются координатами точек вершин в мировой системе координат. В XNA и OpenGL используется правая система мировых координат, а в DirectX - левая. Начало мировой системы координат находится на поверхности экрана в его центре. Для наблюдателя, смотрящего на экран, ось Ox направлена по горизонтали вправо, а ось Oy - по вертикали вверх. Системы отличаются только направлением оси Oz: в правой системе координат она направлена из глубины экрана к пользователю, а в левой - от пользователя в глубину экрана.
Совокупность трехмерных объектов в мировой системе координат называется сценой. Визуализация сцены выполняется при помощи установки камеры, размещение которой также задается относительно мировой системы координат. Положение камеры ассоциируется с положением глаз наблюдателя и с математической точки зрения определяет матрицу вида. Для корректного отображения объектов сцены на экране каждый из них предварительно преобразуется матрицей вида. Обычно камера отодвигается от экрана в положительном направлении оси Oz и несколько приподнимается вверх в положительном направлении оси Oy. Это позволяет установить взгляд наблюдателя на сцену как с балкона театра.
В пространстве имен Microsoft.Xna.Framework.Graphics имеется ряд структур
для хранения информации о вершинах примитива. В данном упражнении мы будем
использовать структуру VertexPositionColor,
инкапсулирующую информацию о координатах и цвете вершины.
Напомним, что любая структура, как и класс, содержит члены-методы и члены-данные
и относится к значимому типу (в противоположность к ссылочномы типу).
Члены-данные струтуры (как и класса) называются полями. В структурах C# конструктор
по умолчанию является предопределенным и его задавать нельзя. Это сделано для
того, что объявление структурной переменной одновременно означает и создание
экземпляра структуры с неявным вызовом конструктора по умолчанию. Зато параметризованных
конструкторов может быть сколько угодно при условии, что каждый из них имеет
уникальную в рамках структуры сигнатуру. В этом случае применение
оператора new обязательно.
Библиотечная структура VertexPositionColor имеет один параметризованный
конструктор вида:
public VertexPositionColor(Microsoft.Xna.Framework.Vector3 position,
Microsoft.Xna.Framework.Graphics.Color color)
Информацию обо всех вершинах примитива можно хранить в массиве, например:
VertexPositionColor[] vertices;
Но здесь есть один нюанс. Дело в том, что при визуализации примитивов информация
о вершинах напрямую передается в графический процессор видеокарты GPU (Graphics
Processor Unit), который не знает, что используется массив структур именно VertexPositionColor. Для разъяснения графическому процессору формата отдельных
полей структуры применяются декларации формата вершины. В XNA
Framework декларация вершины задается классом VertexDeclaration,
конструктор которого имеет вид:
public VertexDeclaration(Microsoft.Xna.Framework.Graphics.GraphicsDevice graphicsDevice,
Microsoft.Xna.Framework.Graphics.VertexElement[] elements)
Массив elements содержит информацию о формате данных, с которыми будет
работать графическое устройство:
Если для хранения данных применяется структура VertexPositionColor,
то описание ее формата хранится в
статическом поле только для чтения VertexPositionColor.VertexElements.
Пользователю (программисту) остается только передать это поле конструктору
класса VertexDeclaration в
качестве второго параметра.
Состав элементов сцены описывается один раз при загрузке приложения. Код загрузки
описания примитивов можно разместить, например, в обработчике события Load формы.
Изменение позиции объектов и их цвет, расчитываемые для каждого кадра визуализации
сцены с помощью мировых матриц смещения, поворота и масштабирования, матриц
проекций и вида, помещают в блок кода Update(), который выполняется
каждый раз в обработчике события Paint.
Визуализация массива примитивов осуществляется с помощью метода класса GraphicsDevice,
имеющего следующее описание
public void DrawUserPrimitives<T>(Microsoft.Xna.Framework.Graphics.PrimitiveType primitiveType, T[] vertexData,
int vertexOffset, int primitiveCount)
T - шаблон типаprimitiveType - тип примитива, задаваемый с использованием
перечисления PrimitiveType vertexData - массив вершин примитиваvertexOffset - смещение от начала массива. Данный параметр
обычно равен нулю. Ненулевые значения применяется, когда визуализируемый
примитив использует примитивы лишь из части массива (например, вершины
разных примитивов хранятся в одном большом общем массиве)primitiveCount - количество примитивов, которые нужно
выбрать для визуализацииТаким образом, для визуализации примитивов приложение должно выполнить примерно следующие шаги:
В обработчике события Load:
presentParams класса PresentationParameters и
определить в его свойствах настройки будущего графического устройства devicedevice типа GraphicsDevice и
настроить его через свойства экземпляра presentParamsvertices структур
типа VertexPositionColor[], где каждый элемент этого массива задает
координаты вершины типа Vector3 и ее цвет типа Colordecl структуры VertexDeclaration,
передав ее конструктору статическое поле-массив VertexPositionColor.VertexElements В обработчике события Paint:
decl свойству VertexDeclaration объекта device для
корректной обработки вершин при отображении сцены на экранеdevice.Clear()device.DrawUserPrimitives()device.Present()На первый взгляд операцию передачи ссылки на объект decl свойству VertexDeclaration объекта device было
бы рациональнее вынести в обработчик события графического
устройства теряются при сбросе методом Reset() и нужно восстанавливать
информацией из decl и presentParams.
И это нужно делать сразу в обработчике события Paint.
Но это еще не все. Дело в том, что все современные видеокарты содержат специализированные GPU -
векторные и пиксельные процессоры, используемые для ускорения операций преобразования
вершин и закраски примитивов. Так как эти процессоры принимают участие при
визуализации любых примитивов, приложение должно запрограммировать их на выполнения
требуемых преобразований. Если этого не сделать, то результат вызова метода device.DrawUserPrimitives() будет непредсказуемым. Отсюда возникает
необходимость программирования вершинных и пиксельных процессоров графического
ускорителя видеокарты.
Для программирования вершинных и пиксельных процессоров GPU служит
язык HLSL (High Level
Язык HLSL хотя и тесно связан с архитектурой графического процессора, но является управляемым C -подобным языком программирования для некоторого виртуального процессора, приближенного к некоторому реальному прототипу. Такой подход напоминает применение промежуточного языка IL в .NET. Компиляция шейдера в систему команд физического процессора происходит непосредственно перед загрузкой шейдера в GPU. Это позволяет несколько абстрагироваться от аппаратной части процессора, генерируя при компиляции промежуточный байт-код, и таким образом расширить семейство видеокарт, на которых может работать графическое приложение.
На первой ступени вершины обрабатываются вершинным процессором по программе, называемой вершинным шейдером. На выходе из вершинного процессора получаются так называемые трансформированные (преобразованные) вершины. К вершинам могут быть "привязаны" различные параметры: цвет вершины, текстурные координаты и так далее. Координаты трансформированных вершин задаются в логической системе однородных координат, называемой clip space.
Однородные координаты вершины определяются четырьмя числами: (x, y, z, w).
Перевод однородных координат в обычные геометрические осуществляется путем
деления первых трех компонентов на четвертый компонент w: (x/w, y/w, z/w).
Например, вершине с однородными координатами (1, 2, 3, 4) в трехмерном пространстве
соответствует точка с координатами (1/4, 2/4, 3/4). Использование четвертого
компонента обусловлено рядом особенностей алгоритмов визуализации трехмерных
изображений, используемых в 3D -графике.
При визуализации двухмерных изображений компонент w обычно полагают равным
1. В этом случае нижнему левому углу клиентской области формы соответствует
точка с координатами (-1, -1, 0, 1), правому верхнему углу клиентской области
- (1, 1, 0, 1), а центру клиентской области - соответственно (0, 0, 0, 1).

На второй ступени графического конвейера видеокарта производит преобразование
координат вершины из логической системы координат в оконную. По умолчанию координаты
трансформируются таким образом, чтобы растянуть изображение на всю поверхность
элемента управления. Managed DirectX через код XNA позволяет
программисту задавать координаты вершин в оконных координатах. В этом случае,
при вызове метода device.DrawUserPrimitives() вершины сразу поступают
на третью стадию графического конвейера, минуя первую и вторую стадии.
На третьей ступени идет сборка примитивов. На этой стадии вершины
объединяются в примитивы. Тип примитивов определяется первым параметром метода device.DrawUserPrimitives(). Так при использовании параметра PrimitiveType.TriangleStrip вершины
трактуются, как опорные точки полосы треугольников. При этом каждый треугольник
из полосы является независимым примитивов и обрабатывается независимо от других
треугольников этой полосы.
На четвертой ступени происходит растеризация примитивов – преобразование каждого примитива в набор пикселей экрана. Параметры внутренних пикселей примитива (например, цвет) определяются путем интерполяции соответствующих параметров вершин вдоль поверхности примитива. Благодаря этой интерполяции при закраске треугольника с разноцветными вершинами образуются красивые цветовые переходы - градиенты цвета.
На пятой ступени происходит обработка пикселей пиксельным процессором с использованием программы, называемой пиксельным шейдером. На вход пиксельному процессору подаются параметры пикселя (цвет, текстурные координаты и т.д.), полученные путем интерполяции соответствующих вершинных параметров вдоль поверхности примитива. После обработки входных параметров, пиксельный процессор возвращает цвет пиксела, который заносится в кадровый буфер. При этом возможно выполнение некоторой простой дополнительной обработки изображения вроде смешивания цветов при эффекте полупрозрачности.
Самой крупной логической единицей HLSL является эффект ( Effect ),
хранящийся в отдельном текстовом файле с расширением .fx. В принципе,
эффект можно считать аналогом материала в 3DS MAX. Каждый эффект состоит
из одной или нескольких техник ( technique ). Техника – это способ
визуализации материала. Например, эффект визуализации мраморного материала
может содержать три техники: технику High для получения изображения
наивысшего качества при низкой производительности, Medium для получения
изображения среднего качества, и Low – максимальная производительность
при низком качестве изображения.
Каждой технике сопоставлен пиксельный и вершинный шейдер, при этом несколько техник могут использовать общий шейдер. Количество техник и их названия могут быть произвольными.
Известным инструментом для разработки шейдеров является RenderMonkey компании AMD. Этот продукт можно скачать по адресу (http://developer.amd.com/gpu/rendermonkey/Pages/default.aspx).
В HLSL все встроенные типы делятся на две большие группы: скалярные и векторные. Скалярные типы данных являются аналогами встроенных типов данных языка C.
| Тип | Описание |
|---|---|
bool |
Логический тип, который может принимать значения true или false |
int |
32-х битное целое число |
half |
16-ти битное число с плавающей точкой |
float |
32-х битное число с плавающей точкой |
double |
64-х битное число с плавающей точкой |
Задавая тип скаларной переменной, мы просто указываем компилятору свое пожелание.
Если текущий ускоритель не поддерживает некоторые типы данных, используемые
в программе, то при компиляции шейдера в машинный код они будут заменены ближайшими
аналогами. Например, тип double может быть заменен на тип float, half или
какой-нибудь иной внутренний тип. Поэтому программист должен стараться избегать
жесткой привязки к точности и допустимому диапазону значений используемого
типа данных. Особенно это актуально для типа int, так как подавляющее
большинство современных ускорителей не поддерживают тип int, в результате
чего он эмулируется посредством одного из вещественных типов. Подобная эмуляция
приводит к существенному падению производительности шейдера.
Большинство данных, используемых в трехмерной графике, является векторами
размерности от 2 до 4 скаларных переменных. Так, координаты точки в трехмерном
пространстве задаются трехмерным вектором, цвет пиксела - четырехмерным вектором
(три цвета и альфа-канал). Соответственно, все современные GPU являются
Для описание векторных данных в HLSL имеются векторные типы размерностью
от 2 до 4. Если сказать обобщенно, то векторный тип из N элементов скалярного
типа type задается с использованием синтаксиса, отдаленно напоминающего
шаблоны из C#:
vector<type, size>
type - имя скалярного типа: bool, int, half, float или double size - размерность вектора: 1, 2, 3 или 4Например, объявление переменной v, являющейся вектором из четырех чисел
типа float, выполняется так:
vector<float, 4> v;
Но на практике обычно используется сокращенная запись
float4 v;
Язык HLSL позволяет инициализировать вектор двумя способами. Первый
способ - перечислить значения вектора в фигурных скобках, например, присвоение
четырехмерному вектору v начального значения выглядит так
float4 v = {0.2, 0.4, 0.6, 0.8};
Другой способ - создать новый вектор с использованием конструктора и присвоить
его вектору v:
float4 v = float4(0.2, 0.4, 0.6, 0.8);
Любой вектор имеет множество конструкторов, которые могут принимать в качестве
параметров как переменные скалярных типов, так и - векторных. Единственное
ограничение: общее количество всех компонентов векторных и скалярных типов
должно быть равно N. Подобное многообразие конструкторов дает программисту
большую гибкость при инициализации векторов. Например, вот как последовательно
можно создать векторы разных размерностей:
float2 a = {0.1, 0.2}; // Создаем двухмерный вектор
float2 b = float2(0.3, 0.4); // Создаем еще один двухмерный
float3 c = float3(b, 1.0); // Создаем трехмерный вектор
float4 d = float4(0.7, c); // Создаем четырехмерный вектор
float4 e = float4(a, b); // Создаем еще один четырехмерный вектор
Данные в 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
}
Мы получили функции для вершинного и пиксельного процессора - вершинный и
пиксельный шейдеры. Заключительный этап написания эффекта - создание
техники (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 {используемый профиль} {пиксельный шейдер};
Профиль шейдера (vs_1_1, Pixel
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 |
Большинство видеокарт поддерживает несколько профилей вершинных и пиксельных шейдеров. В результате каждый разработчик сталкивается с проблемой выбора используемого профиля. Чаще всего выбор версии шейдеров определяется минимальными требованиями к приложению.
Допустим, необходимо, чтобы наша программа могла работать на видеокартах класса 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.Noneplatform – значение перечислимого типа 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();
}
Теперь создадим обработчики нужных событий и заполним их кодом.
Визуализация примитивов, использующих эффект, начинается с вызова метода effect.Begin() объекта
эффекта. Далее приложение должно перебрать все проходы
текущей техники (коллекция effect.CurrentTechnique.Passes ) и вызвать метод pass.Begin() для каждого прохода. Внутри прохода нужно визуализировать
примитивы с использованием метода device.DrawUserPrimitives(). Визуализацию
прохода нужно закончить вызовом метода pass.End(). По окончанию визуализации
всего эффекта приложение должно вызвать метод effect.End().
Через контекстное меню
перейдите в режим View Designer формы, выделите на ней объект xnaPanel1 и
откройте панель Properties в
режиме Events
Найдите событие Paint объекта xnaPanel1 и
двойным щелчком на его поле справа создайте обработчик, который заполните
такprivate void xnaPanel1_Paint(object sender, PaintEventArgs e)
{
if (closing)
return;
try
{
if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.Lost)
throw new DeviceLostException();
if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.NotReset)
device.Reset(presentParams);
// Очищаем область экрана
device.Clear(XNAGraphics.Color.CornflowerBlue);
device.RenderState.CullMode = CullMode.None;
device.VertexDeclaration = decl;
// Задаем координаты вершин из установленного в группе панелей цвета
vertices[0] = new VertexPositionColor(new Vector3(0.0f, 0.8f, 0.0f), new XNAGraphics.Color(
vertex1Color.BackColor.R, vertex1Color.BackColor.G, vertex1Color.BackColor.B));
vertices[1] = new VertexPositionColor(new Vector3(0.8f, -0.8f, 0.0f), new XNAGraphics.Color(
vertex2Color.BackColor.R, vertex2Color.BackColor.G, vertex2Color.BackColor.B));
vertices[2] = new VertexPositionColor(new Vector3(-0.8f, -0.8f, 0.0f), new XNAGraphics.Color(
vertex3Color.BackColor.R, vertex3Color.BackColor.G, vertex3Color.BackColor.B));
effect.Begin();
foreach(EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
pass.End();
}
effect.End();
device.Present();
}
catch (DeviceNotResetException)
{
Invalidate();
}
catch (DeviceLostException)
{
closing = true; // Больше не выполнять OnPaint()
string title = "Сбой графического устройства";
string message = "Работа программы будет завершена.\n"
+ "Закройте все ненужные программы\n"
+ "и повторите запуск этого приложения";
MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Stop);
Application.Idle += new EventHandler(Application_Idle);
return;
}
catch (Exception ext) // Все другие исключения
{
closing = true;
MessageBox.Show(ext.Message);
Application.Idle += Application_Idle;
}
}
Выделите на форме объект xnaPanel1 и
через панель Properties в режиме Events создайте обработчик
события Resize, который заполните такprivate void xnaPanel1_Resize(object sender, EventArgs e)
{
// Устанавливаем размеры заднего буфера по клиентской области панели
presentParams.BackBufferWidth = xnaPanel1.ClientSize.Width;
presentParams.BackBufferHeight = xnaPanel1.ClientSize.Height;
// Сбрасываем устройство и применяем к нему новые параметры
// Если окно не минимизировано
if (this.WindowState != FormWindowState.Minimized)
device.Reset(presentParams);
}
Выделите саму форму
щелчком на ее заголовке и
через панель Properties в режиме Events создайте обработчик
события FormClosed, который заполните такprivate void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
// Удаляем (освобождаем) устройство
device.Dispose();
// На всякий случай присваиваем ссылке на устройство значение null
device = null;
}
Осторожно (чтобы не
создать обработчик) выделите все три кнопки на форме (удерживая клавишу Ctrl ) и через панель Properties в
режиме Events создайте
обработчик события Click с именем vertexColor_Click, который заполните
такprivate void vertexColor_Click(object sender, EventArgs e)
{
Button button = (Button)sender;
colorDialog1.Color = button.BackColor;
if (colorDialog1.ShowDialog() == DialogResult.OK)
{
button.BackColor = colorDialog1.Color;
xnaPanel1.Invalidate();
}
}
В панели Solution
Explorer выделите узел проекта Application2 и через контекстное меню сделайте проект
стартовым. Запустите приложение - должен получиться такой результат
ColorFill.fx из
прилагаемой к работе папки Source, не редактируя его. Незабудьте в этом
случае изменить свойство файла Copy to Output Directory на
значение Copy if newer.Если вы все делали так как описано, то исполнимые сборки для упражнений 1
и 2 должны находиться относительно
папки решений FormsXNA в подпапках:
FormsXNA\Application1\bin\Debug\Application1.exe FormsXNA\Application2\bin\Debug\Application2.exe
В этом разделе, для тренировки, мы добавим к проекту простое приложение, запускающее сборки с выполненными упражнениями, чтобы легче было их демонстрировать.
Добавьте к решению
новый проект с именем StartForm и сделайте его стартовым
Переименуйте файл формы Form1.cs в StartForm.cs и откройте
его в режиме View Designer и поместите на форму
два экземпляра компонента LinkLabel
Создайте для них
обработчики события LinkClicked и заполните их следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace StartApp
{
public partial class StartForm : Form
{
System.Diagnostics.Process exe = new System.Diagnostics.Process();
String path; // Путь к папке с решением
public StartForm()
{
InitializeComponent();
path = Directory.GetCurrentDirectory();// Получаем путь к сборке StartForm
int pos = path.LastIndexOf(@"\bin\");
path = path.Substring(0, pos);// Убираем 2 последних секции пути
pos = path.LastIndexOf(@"\");
path = path.Substring(0, pos);// Убираем еще одну секцию
}
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Directory.SetCurrentDirectory(path + @"\Application1\bin\Debug");// Путь к сборке
exe.StartInfo.FileName = "Application1.exe";
System.Diagnostics.Debug.WriteLine(path); // Для отладки в окно Output
exe.Start();
}
private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Directory.SetCurrentDirectory(path + @"\Application2\bin\Debug");// Путь к сборке
exe.StartInfo.FileName = "Application2.exe";
System.Diagnostics.Debug.WriteLine(path); // Для отладки в окно Output
exe.Start();
}
}
}
Код будет работать при условии, что сборка стартовой формы находится в своем
каталоге bin дерева решения.

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Работа выполнена на основе материалов С.А. Гайдукова, приведенных в источнике http://www.intuit.ru/department/se/intxna
Создайте новое решение
и новый проект командой File/New/Project, заполнив окно мастера так
Переименуйте файл формы Form1.cs в MainForm.cs
Выделите форму в конструкторе
форм и в панели Properties в свойстве Text установите заголовок
окна " Упражнение 1 "
Командой Project/Add Reference вызовите
окно добавления ссылок и на вкладке .NET выберите библиотечную
сборку Microsoft.Xna.Framework.dll версии 3.0
В этой библиотеке находится множество компонентов 3D -графики, размещенные
в нескольких пространствах имен, в том числе в интересующем нас пространстве
имен Microsoft.Xna.Framework.Graphics

Прежде всего воспользуемся классом GraphicsDevice, инкапсулирующим
средства управления графическим устройством - видеокартой компьютера. Конструктор
этого класса описывается так
public GraphicsDevice(Microsoft.Xna.Framework.Graphics.GraphicsAdapter adapter, Microsoft.Xna.Framework.Graphics.DeviceType deviceType, System.IntPtr renderWindowHandle, Microsoft.Xna.Framework.Graphics.PresentationParameters presentationParameters)
GraphicsAdapter,
соответствующее выбранной для работы видеокарте. Современные компьютеры содержат
несколько видеокарт, поэтому проще всего выбрать свойства GraphicsAdapter.DefaultAdapter DeviceType,
которое имеет значенияPanel ), который будет использоваться для вывода графических
изображенийPresentationParameters,
свойства которого определяют настройки представления изображения на экранеДля создания и настройки графического устройства нужно объявить в классе MainForm поле,
зарегистрировать для события Load формы обработчик, в котором создать
экземпляр устройства и настроить его с помощью свойств соответствующих объектов.
Вызовите контекстное
меню формы MainForm, щелкнув на ней правой кнопкой мыши, и выполните команду View Code
Подключите иснструкцией using к файлу MainForm.cs пространство имен Microsoft.Xna.Framework.Graphics
Объявите в классе MainForm поле типа GraphicsDevice с именем deviceusing 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);
}
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);
}
}
}
Прежде, чем создать объект графического устройства, мы создаем вспомогательный
объект presentParams, через свойства которого устанавливаем настройки
для будущего устройства. Размеры заднего буфера можно не задавать, поскольку
они по умолчанию в объекте presentParams установлены по умолчанию в
значение 0, а это значит, что устройство device само
вычислит их. Передним буфером считается экран, а задний буфер добавляется для
устранения мерцания, в итоге при добавлении одного заднего буфера обеспечивается
режим двойной буферизации.
Таким образом, в приведенном коде мы создали графическое устройство со следующими характеристиками:
Теперь нужно прописать код очистки графических буферов перед рисованием нового
изображения. Для этого существует метод Clear() графического устройства,
имеющий несколько перегрузок. Пока воспользуемся упрощенной перегрузкой этого
метода,
которая очищает только экранный буфер заданным цветом:
public void Clear(Microsoft.Xna.Framework.Graphics.Color color)
Куда же вставить вызов функции очистки графического устройства? Когда происходит
разрушение экрана за счет сворачивания окна или заслонения другими окнами,
то при необходимости его восстановления операционная система генерирует событие Paint унаследованного формой класса Control и мы можем либо подписать
обработчик на это событие и туда вставить очистку, либо перекрыть виртуальный
метод диспетчеризации OnPaint() формы
и в него вставить вызов очистки. Воспользуемся вторым вариантом...
В классе MainForm как
член класса наберите ключевое слово override, после введения
символа пробела в открывшемся окне подсказчика кода IntalliSense выберите
метод диспетчеризации OnPaint() и
завершите набор нажатием клавиши Enter
В перекрытом методе
диспетчеризации OnPaint() введите код вызова метода очистки устройства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);
}
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);
}
}
}
Обратите внимание, что мы были вынуждены прописать полное имя структуры Color во избежании конфликта имен, поскольку точно такая же структура находится в
подключенном к файлу пространстве имен System.Drawing.
При завершении работы нашего приложения
сборщик мусора GC среды исполнения CLR попытается автоматически освободить
графическое устройство. Но сделать это корректно ему не удастся. Дело в том,
классы XNA являются
тонкой оболочкой API DirectX, а сам DirectX освобождает только те объекты,
которые были созданы в его потоке. Сборщик мусора же для своей работы создает
новый отдельный поток, который не сможет корректно удалить объекты DirectX,
в том числе созданный нами объект device. В связи с этим нам нужно самим
позаботиться об удалении таких критичных объектов в нашем приложении.
В конструкторе MainForm() одноименного класса
подпишитесь на обработчик события FormClosed и заполните обработчик кодом
удаления устройства deviceusing 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);
}
}
}
Запустите приложение
на выполнениеПока ничего не произошло и мы не видим графического окна в клиентской области
формы, хотя событие Paint исправно срабатывает, в чем можно убедиться,
если установить точку останова (Breakpoint) в перекрытом методе диспетчеризации OnPaint().

Всe дело в том, что мы используем двойную буферизацию, то есть наше приложение
выполняет все графические построения в заднем невидимом вспомогательном буфере.
После окончания визуализации необходимо скопировать информацию из этого вспомогательного
буфера на форму. Эту операцию выполняет метод Present() класса GraphicsDevice.
Добавьте вызов
метода Present() в перекрытый метод диспетчеризации OnPaint()protected override void OnPaint(PaintEventArgs e)
{
device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
Снова запустите
программу на выполнение - на экране появится окно, клиентская область которого
закрашена синим цветом, что нам и требовалось
Теперь попробуем применить более сложную перегрузку метода Clear() для
избирательной очистки экрана и нарисовать с ее помощью шахматную доску:
public void Clear(Microsoft.Xna.Framework.Graphics.ClearOptions options, Microsoft.Xna.Framework.Graphics.Color color, float depth, int stencil, Microsoft.Xna.Framework.Rectangle[] regions)
Target перечисления ClearOptions.
Остальные флаги DepthBuffer и Stencil этого перечисления используются
для очистки соответственно буфера глубины и буфера шаблона, и будут рассмотрены
далееMicrosoft.Xna.Framework.Graphics.Color, являющейся аналогом структуры System.Drawing.Color. Необходимость дублирования обусловлено стремлением
разработчиков Microsoft сделать XNA Framework переносимой на другие платформыMicrosoft.Xna.Framework.Rectangle,
задающих прямоугольные области экрана, которые должны быть очищены. Области
экрана задаются в оконных координатах клиентской области формы - начало координат
расположено в ее левом верхнем углуДля рисования шахматной доски вначале мы очистим экран белым цветом, затем создим массив областей экрана, соответствующих клеткам доски коричневого цвета и ещe раз очистим экран, но уже коричневым цветом.
Модифицируйте перекрытый
метод OnPaint() следующим образомprotected override void OnPaint(PaintEventArgs e)
{
// Очищаем экран белым цветом
device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke);
// Создаeм массив областей закраски, соответствующих коричневым клеткам
Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32];
int k = 0; // Счетчик элементов массива
// Перебираем коричневые клетки шахматной доски
for (int j = 0; j < 8; j++) // Строки шахматной доски
for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски
{
// Заносим в массив параметры рисования очередной клетки
rects[k] = new Microsoft.Xna.Framework.Rectangle(
i * this.ClientSize.Width / 8, // Отступ по горизонтали
j * this.ClientSize.Height / 8, // Отступ по вертикали
this.ClientSize.Width / 8, // Ширина клетки
this.ClientSize.Height / 8); // Высота клетки
k++; // Увеличиваем счетчик
}
// Закрашиваем все области из массива rects коричневым цветом
device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
Graphics.Color.Brown, 0.0f, 0, rects);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
Запустите приложение
- на экране появится окно, раскрашенное как шахматная доска
Попробуйте поизменять размеры окна и отметьте, что программа отображает доску непонятным образом как при уменьшении, так и при увеличении окна. Устраним сначала проблему, возникающую при уменьшении окна.
Начнем с того, что
при уменьшении окна необходимости в дорисовке экрана нет и событие Paint наследуемого
формой класса Control не срабатывает. В принципе, для борьбы с этим
недоразумением мы могли бы перекрыть
метод диспетчеризации OnResize() формы (или подписаться
на событие Resize )
и вставить туда вызов метода Invalidate(), принудительно генерирующего
событие Paint. Однако существует гораздо более элегантное решение:
если установить у формы стиль ResizeRedraw, то при изменении размера
формы будет автоматически генерироваться событие Paint.
Добавьте
в обработчик события Load установку стиля формы ResizeRedrawvoid 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
Запустите приложение
и поизменяйте размеры окнаТеперь экран при уменьшении размеров стал перерисовываться. Но здесь стала очевидной другая проблема: при изменении размеров появились заметные мигания окна. Чтобы эти мигания стали еще заметнее, выполните следующее:
В рабочей области редактора
кода вызовите контекстное меню и выполните команду View Designer (или
просто выполните команду меню оболочки View/Designer )
Выделите форму щелчком
мыши и в панели Properties установите свойство BackColor формы в значение Green
Запустите приложение
и поизменяйте размеры окна - сейчас мигания зеленого цвета на фоне шахматной
доски стали более заметныДело здесь в том, что перед каждым вызовом события Paint автоматически
вызывается виртуальный метод OnPaintBackground() наследуемого формой
класса Control, который
очищает экран цветом BackColor. Эта функциональность позволяет разработчику,
использующему обычный GDI+, не заботится об очистке экрана, однако в
нашем случае такая "самовольная" очистка
формы стандартными средствами Windows приводит лишь к мерцанию. Следовательно,
нам необходимо запретить форме закрашивать экран перед вызовом обработчика
события Paint.
Это делается установкой для формы стиля Opaque.
Добавьте в обработчик
события Load установку стиля формы Opaquevoid 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Запретить очистку экрана средствами GDI+
this.SetStyle(ControlStyles.Opaque, true);
}
Запустите приложение
- теперь мигания формы при изменении ее размеров исчезлиИ так, первую проблему мы решили, но осталась вторая, гораздо более неприятная
- некорректное масштабирование шахматной доски при изменении размера окна.
Дело в том, что при создании графического устройства мы задаeм размер вспомогательного
буфера, используемого при двойной буферизации, равным размеру клиентской области
окна и к тому же только один раз (в обработчике события Load формы).
Когда мы изменяем размер окна, его клиентская область так же изменяется. А
вот размер вспомогательного буфера графического устройства остаeтся неизменным.
Получается, что при изменении размеров окна происходит рассинхронизация между
размерами клиентской области окна просмотра и вспомогательного буфера-источника,
в котором рисуется изображение. В результате, приложение начинает
работать некорректно.
Чтобы синхронно подстраивать параметры графического устройства под новые размеры
окна, нужно перекрыть метод диспетчеризации OnResize() формы, включить в него новые
настройки объекта presentParams и перезапустить объект device его
методом Reset(). Но прежде всего нужно ссылку на объект presentParams сделать
видимой в методе диспетчеризации OnResize(), т.е. вынести ее объявление в поле класса.
На основе вышесказанного
внесите следующие изменения в класс MainFormusing 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;
// Объявим объект настроек графического устройства
PresentationParameters presentParams;
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 = 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Запретить очистку экрана средствами GDI+
this.SetStyle(ControlStyles.Opaque, true);
}
protected override void OnResize(EventArgs e)
{
// Устанавливаем размеры заднего буфера по клиентской области окна формы
presentParams.BackBufferWidth = this.ClientSize.Width;
presentParams.BackBufferHeight = this.ClientSize.Height;
// Сбрасываем устройство и применяем к нему новые параметры
device.Reset(presentParams);
base.OnResize(e);
}
protected override void OnPaint(PaintEventArgs e)
{
// Очищаем экран белым цветом
device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke);
// Создаeм массив областей закраски, соответствующих коричневым клеткам
Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32];
int k = 0; // Счетчик элементов массива
// Перебираем коричневые клетки шахматной доски
for (int j = 0; j < 8; j++) // Строки шахматной доски
for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски
{
// Заносим в массив параметры рисования очередной клетки
rects[k] = new Microsoft.Xna.Framework.Rectangle(
i * this.ClientSize.Width / 8, // Отступ по горизонтали
j * this.ClientSize.Height / 8, // Отступ по вертикали
this.ClientSize.Width / 8, // Ширина клетки
this.ClientSize.Height / 8); // Высота клетки
k++; // Увеличиваем счетчик
}
// Закрашиваем все области из массива rects коричневым цветом
device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
Graphics.Color.Brown, 0.0f, 0, rects);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
}
}
Операция сброса устройства, выполняемая при вызове метода Reset(),
является очень медленной операция. Никогда не вставляйте еe без причины в обработчик
события Paint или перекрытый метод диспетчеризации OnPaint(). Они
вызываются очень часто и это приведeт к заметному падению производительности.
В нашем случае эта операция вставлена в перекрытый метод диспетчеризации OnResize() и
выполняется только при изменении пользователем размеров формы, что бывает редко.
Выполните приложение
- теперь рисование при изменениях окна работает нормально
Но здесь скрыта еще одна проблема. Если уменьшить вертикальный размер клиентской
области формы до нуля (уменьшить горизонтальный размер до нуля мешают системные
кнопки и заголовок окна), то приложение завершится аварийно именно при попытке
установки нулевых размеров заднего буфера в графическом устройстве. Для решения
этой проблемы нужно ограничить минимальный размер клиентской области хотя бы
одним пикселом. Это легко можно сделать при помощи свойства MinimumSize формы,
которое задает минимальные размеры окна (но не клиентской области!).
Properties будет не точным. Размер
клиентской области, который отведет система при заданном размере окна в целом,
зависит от множества факторов: установленной операционной
системы, пользовательских настроек и т.п. Правилнее устанавливать значение
свойства MinimumSize программно
посредством метода формы SizeFromClientSize(),
который возвращает размер окна при заданном размере именно клиентской области.
Добавьте в обработчик
события Load следующий код, устанавливающий минимально допустимый
для изменения размер окна при заданном размере клиентской области 1x1 пикселовvoid MainForm_Load(object sender, EventArgs e)
{
// Создаем объект представления для настройки графического устройства
//PresentationParameters presentParams = new 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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Запретить очистку экрана средствами GDI+
this.SetStyle(ControlStyles.Opaque, true);
// Вычисляем размер окна при размерах клиентской области 1x1 пикселов.
// Полученное значение присваиваем свойству MinimumSize
this.MinimumSize = this.SizeFromClientSize(new Size(1, 1));
}
Можно запустить приложение и убедиться, что при интерактивном уменьшении размеров клиентской области остается еще один пиксел в запасе и приложение продолжает устойчиво работать. Но есть еще одна скрытая родственная лазейка для получения нулевых размеров клиентской области - минимизация окна с помощью системной кнопки. Для ее решения сброс графического устройства для установки новых размеров буфера нужно выполнять только после проверки состояния окна, убедившись, что оно не минимизировано.
Добавьте в перекрытый
метод диспетчеризации OnResize() следующую проверку состояния окнаprotected override void OnResize(EventArgs e)
{
// Устанавливаем размеры заднего буфера по клиентской области окна формы
presentParams.BackBufferWidth = this.ClientSize.Width;
presentParams.BackBufferHeight = this.ClientSize.Height;
// Сбрасываем устройство и применяем к нему новые параметры
// Если окно не минимизировано
if (this.WindowState != FormWindowState.Minimized)
device.Reset(presentParams);
base.OnResize(e);
}
Запустите приложение,
минимизируйте окно - приложение продолжает устойчиво работать!Операционная система Windows является многозадачной системой, в которой одновременно могут выполняться несколько задач и делить между собой одни и те же ресурсы. Существует опасность, что настройки графического устройства, установленные нашим приложением XNA, в любой момент могут сбиться другим подобным приложением. Такая ситуация называется потерей устройства (Device Lost). Это легко проверить, если при запущенном приложении XNA попытаться изменить разрешение экрана - приложение завершится аварийно.
Отсюда возникает задача о восстановлении потерянной информации, рассмотрением
которой мы сейчас и займемся. Информацию о состоянии, в котором в данный
момент находится устройство, можно получить из свойства GraphicsDeviceStatus нашего
объекта (экземпляра класса GraphicsDevice ), которое имеет одно из значений
одноименного перечисления GraphicsDeviceStatus:
Normal - графическое устройство работает нормальноNotReset - графическое устройство потеряно, но может быть восстановлено
методом Reset() объекта устройстваLost - графическое устройство потеряно и пока не может быть восстановленоПри восстановимой потере устройства приложение выдает исключение Microsoft.Xna.Framework.Graphics.DeviceNotResetException,
а при невосстановимой - Microsoft.Xna.Framework.Graphics.DeviceLostException.
Если сгенерировано исключение DeviceNotResetException, то приложение
должно восстановить устройство методом device.Reset() и снова перерисовать
изображение путeм вызова метода Invalidate(), в противном случае просто
не реагировать на событие Paint, требующее обновить экран.
На основе вышесказанного
модифицируйте обработку события Paint следующим образомbool closing = false;
protected override void OnPaint(PaintEventArgs e)
{
if (closing) return;
try // Попытка
{
// Очищаем экран белым цветом
device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke);
// Создаeм массив областей закраски, соответствующих коричневым клеткам
Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32];
int k = 0; // Счетчик элементов массива
// Перебираем коричневые клетки шахматной доски
for (int j = 0; j < 8; j++) // Строки шахматной доски
for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски
{
// Заносим в массив параметры рисования очередной клетки
rects[k] = new Microsoft.Xna.Framework.Rectangle(
i * this.ClientSize.Width / 8, // Отступ по горизонтали
j * this.ClientSize.Height / 8, // Отступ по вертикали
this.ClientSize.Width / 8, // Ширина клетки
this.ClientSize.Height / 8); // Высота клетки
k++; // Увеличиваем счетчик
}
// Закрашиваем все области из массива rects коричневым цветом
device.Clear(ClearOptions.Target, Microsoft.Xna.Framework.
Graphics.Color.Brown, 0.0f, 0, rects);
// Копируем задний буфер на экран
device.Present();
base.OnPaint(e);
}
// Откаты
catch (DeviceNotResetException) // Устройство можно восстановить
{
device.Reset(presentParams);
this.Invalidate();
}
catch (DeviceLostException) // Устройство нельзя восстановить
{
closing = true; // Больше не выполнять OnPaint()
string title = "Сбой графического устройства";
string message = "Работа программы будет завершена.\n"
+ "Закройте все ненужные программы\n"
+ "и повторите запуск этого приложения";
MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Stop);
Application.Idle += new EventHandler(Application_Idle);
return;
}
catch (Exception ext) // Все другие исключения
{
closing = true;
MessageBox.Show(ext.Message);
Application.Idle += Application_Idle1;
}
}
void Application_Idle(object sender, EventArgs e)
{
this.Close(); // Закрывает окно, у нас оно же и главное окно приложения
}
void Application_Idle1(object sender, EventArgs e)
{
Application.Exit(); // Завершает работу приложения (приведено для разнообразия)
}
Запустите приложение
и попробуйте изменить разрешение экрана, чтобы поймать исключение невосстановимой
потери устройстваВ этом случае будет выдано окно с сообщением

В упражнении 1 наше приложение осуществляло монопольный вывод графической
информации на всю поверхность формы. Такой подход не позволяет размещать на
форме элементы управления.
Можно попробовать использовать вспомогательные диалоговые
окна или плавающие панели инструментов, но это не очень красивое решение проблемы.
Гораздо интереснее было бы научиться выводить информацию не на саму форму,
а на элементы управления, размещенные на ней (к примеру, на элемент управления Panel ). Это позволило бы на освободившемся пространстве размещать другие
нужные элементы пользовательского интерфейса.
Все элементы управления наследуют от класса Control, в том числе класс Form и Panel.
Ранее при управлении экземпляром класса Form мы фактически
использовали методы и свойства класса Control. В данном упражнении мы
с таким же успехом можем управлять экземпляром класса Panel, помещенным
на форму.
Единственная сложность состоит в том, что при управлении экземпляром Form наш код размещался внутри его расширения - классе MainForm, и имел доступ ко всем общедоступным ( public ) и защищенным
( protected ) свойствам и методам, унаследованным, в конечном итоге, от класса Control. А если попытаться просто управлять экземпляром класса Panel,
помещенным на форму, то видимыми из расширения класса Form в объекте Panel (и,
соответственно, в Control ) будут только общедоступные члены. Чтобы
получить полный доступ к нужным сервисам класса Control, нужно создать
расширение класса Panel и упаковать в нем вызовы защищенных членов
в общедоступные.
Фактически, нам нужно создать новый элемент управления, которые называют пользовательскими (в отличии от стандартных библиотечных).
Закройте в рабочей
области оболочки все редактируемые ранее документы командой меню Window/Close All Documents
Командой File/Add/New Project вызовите
мастер добавления в решение нового проекта с именем XNAPanel и
заполните его окно так
Через панель Solution
Explorer переименуйте автоматически созданный файл Class1.cs на XNAPanel.cs
Командой Project/Add Reference подключите
к проекту библиотечные сборки System.Windows.Forms.dll и System.Drawing.dll,
а в начало файла XNAPanel.cs вставьте инструкции:using System.Windows.Forms; using System.Drawing;
Модифицируйте XNAPanel.cs следующим образомusing System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace XNAPanel
{
public class XNAPanel : Panel
{
// Конструктор по умолчанию
public XNAPanel()
{
// Изменим цвет фона панели, чтобы визуально отличить от других
BackColor = Color.CornflowerBlue;
// Установим минимальный размер,
// чтобы устранить возможное исключение
MinimumSize = new Size(1, 1);
}
// Сделаем общедоступным нужный нам защищенный метод
public new void SetStyle(ControlStyles flag, bool value)
{
base.SetStyle(flag, value);
}
}
}
Командой меню Build/Build XNAPanel откомпилируйте
данный проектМы создали библиотечный пользовательский элемент управления для вывода графической информации XNA, который теперь можно размещать на форме.
Командой File/Add/New Project вызовите
мастер добавления в решение нового проекта с именем Application2 и
заполните его окно так
После того, как мастер создаст новый проект и выведет визуальное представление
формы в рабочую область, можно убедиться, что в панели инструментов Toolbox появилась
новая вкладка XNAPanel Components с пиктограммой компонета XNAPanel.
Через Обозреватель
решений переименуйте файл Form1.cs проекта Application2 в MainForm.cs,
поменяйте заголовок окна Text="Упражнение 2" и из панели инструментов Toolbox поместите
на форму экземпляр компонента SplitContainer (можно
просто выполнить двойной щелчок мышью на компоненте). Убедитесь, что свойство Dock объекта splitContainer1 имеет значение Fill, т.е.
что объект занимает всю клиентскую область формы
Установите для объекта splitContainer1 свойство BorderStyle в значение FixedSingle, чтобы создать черные рамки вокруг панелей
элемента
Выделите курсором левую
панель Panel1 объекта splitContainer1, поместите в нее из панели Toolbox экземпляр
нашего компонента XNAPanel и установите его свойство Dock в значение Fill,
чтобы он занял всю область объекта Panel1
Выделите курсором
правую панель объекта splitContainer1, поместите на нее экземпляр
компонента GroupBox и разверните его на всю панель установкой свойства Dock в
значение Fill. Присвойте объекту groupBox1 заголовок " Параметры "
через его свойство Text
Выделите объект groupBox1 и
добавьте в него три текстовых метки Label с надписями
Поместите напротив
этих меток три экземпляра компонента Button и присвойте им имена vertex1Color, vertex2 Color и vertex3Color
Выделите все три экземпляра
компонента Button, очистите для них содержимое свойства Text и
установите свойство Size равным ( 30 ; 20 )
Настройте
для каждой кнопки уникальный цвет фона через свойство BackColor
Добавьте к форме экземпляр
невизуального компонента ColorDialog, пиктограмма которого будет размещена
в подвале рабочей области ( Workspace ) дизайнера формыВ результате форма в режиме проектирования примет следующий вид

Средствами XNA мы нарисуем примитивный треугольник на экземпляре нашего
компонента XNAPanel, цвет вершин которого мы будем выбирать из диалоговой панели.
Для этого, как и ранее, нужно добавить в класс формы несколько
вспомогательных полей, создать обработчики событий Load, FormClosed, Paint и т.д. и заполнить их соответствующим кодом. Но прежде, чем приступить
к программированию обработчиков, познакомимся с новой техникой, которую мы
будем использовать в этом упражнении для управления
видеокартой на низком уровне.
Примитивом называется графический объект - точка, прямая линия, треугольник,
которые используются для построения более сложных объектов. В XNA Framework
поддерживает шесть типов примитивов, заданных структурой PrimitiveType:
список точек PointList, список линий LineList, полоса линий LineStrip,
список треугольников TriangleList, полоса треугольников TriangleStrip и
веер треугольников TriangleFan.
Примитивы описываются координатами точек вершин в мировой системе координат. В XNA и OpenGL используется правая система мировых координат, а в DirectX - левая. Начало мировой системы координат находится на поверхности экрана в его центре. Для наблюдателя, смотрящего на экран, ось Ox направлена по горизонтали вправо, а ось Oy - по вертикали вверх. Системы отличаются только направлением оси Oz: в правой системе координат она направлена из глубины экрана к пользователю, а в левой - от пользователя в глубину экрана.
Совокупность трехмерных объектов в мировой системе координат называется сценой. Визуализация сцены выполняется при помощи установки камеры, размещение которой также задается относительно мировой системы координат. Положение камеры ассоциируется с положением глаз наблюдателя и с математической точки зрения определяет матрицу вида. Для корректного отображения объектов сцены на экране каждый из них предварительно преобразуется матрицей вида. Обычно камера отодвигается от экрана в положительном направлении оси Oz и несколько приподнимается вверх в положительном направлении оси Oy. Это позволяет установить взгляд наблюдателя на сцену как с балкона театра.
В пространстве имен Microsoft.Xna.Framework.Graphics имеется ряд структур
для хранения информации о вершинах примитива. В данном упражнении мы будем
использовать структуру VertexPositionColor,
инкапсулирующую информацию о координатах и цвете вершины.
Напомним, что любая структура, как и класс, содержит члены-методы и члены-данные
и относится к значимому типу (в противоположность к ссылочномы типу).
Члены-данные струтуры (как и класса) называются полями. В структурах C# конструктор
по умолчанию является предопределенным и его задавать нельзя. Это сделано для
того, что объявление структурной переменной одновременно означает и создание
экземпляра структуры с неявным вызовом конструктора по умолчанию. Зато параметризованных
конструкторов может быть сколько угодно при условии, что каждый из них имеет
уникальную в рамках структуры сигнатуру. В этом случае применение
оператора new обязательно.
Библиотечная структура VertexPositionColor имеет один параметризованный
конструктор вида:
public VertexPositionColor(Microsoft.Xna.Framework.Vector3 position,
Microsoft.Xna.Framework.Graphics.Color color)
Информацию обо всех вершинах примитива можно хранить в массиве, например:
VertexPositionColor[] vertices;
Но здесь есть один нюанс. Дело в том, что при визуализации примитивов информация
о вершинах напрямую передается в графический процессор видеокарты GPU (Graphics
Processor Unit), который не знает, что используется массив структур именно VertexPositionColor. Для разъяснения графическому процессору формата отдельных
полей структуры применяются декларации формата вершины. В XNA
Framework декларация вершины задается классом VertexDeclaration,
конструктор которого имеет вид:
public VertexDeclaration(Microsoft.Xna.Framework.Graphics.GraphicsDevice graphicsDevice,
Microsoft.Xna.Framework.Graphics.VertexElement[] elements)
Массив elements содержит информацию о формате данных, с которыми будет
работать графическое устройство:
Если для хранения данных применяется структура VertexPositionColor,
то описание ее формата хранится в
статическом поле только для чтения VertexPositionColor.VertexElements.
Пользователю (программисту) остается только передать это поле конструктору
класса VertexDeclaration в
качестве второго параметра.
Состав элементов сцены описывается один раз при загрузке приложения. Код загрузки
описания примитивов можно разместить, например, в обработчике события Load формы.
Изменение позиции объектов и их цвет, расчитываемые для каждого кадра визуализации
сцены с помощью мировых матриц смещения, поворота и масштабирования, матриц
проекций и вида, помещают в блок кода Update(), который выполняется
каждый раз в обработчике события Paint.
Визуализация массива примитивов осуществляется с помощью метода класса GraphicsDevice,
имеющего следующее описание
public void DrawUserPrimitives<T>(Microsoft.Xna.Framework.Graphics.PrimitiveType primitiveType, T[] vertexData,
int vertexOffset, int primitiveCount)
T - шаблон типаprimitiveType - тип примитива, задаваемый с использованием
перечисления PrimitiveType vertexData - массив вершин примитиваvertexOffset - смещение от начала массива. Данный параметр
обычно равен нулю. Ненулевые значения применяется, когда визуализируемый
примитив использует примитивы лишь из части массива (например, вершины
разных примитивов хранятся в одном большом общем массиве)primitiveCount - количество примитивов, которые нужно
выбрать для визуализацииТаким образом, для визуализации примитивов приложение должно выполнить примерно следующие шаги:
В обработчике события Load:
presentParams класса PresentationParameters и
определить в его свойствах настройки будущего графического устройства devicedevice типа GraphicsDevice и
настроить его через свойства экземпляра presentParamsvertices структур
типа VertexPositionColor[], где каждый элемент этого массива задает
координаты вершины типа Vector3 и ее цвет типа Colordecl структуры VertexDeclaration,
передав ее конструктору статическое поле-массив VertexPositionColor.VertexElements В обработчике события Paint:
decl свойству VertexDeclaration объекта device для
корректной обработки вершин при отображении сцены на экранеdevice.Clear()device.DrawUserPrimitives()device.Present()На первый взгляд операцию передачи ссылки на объект decl свойству VertexDeclaration объекта device было
бы рациональнее вынести в обработчик события графического
устройства теряются при сбросе методом Reset() и нужно восстанавливать
информацией из decl и presentParams.
И это нужно делать сразу в обработчике события Paint.
Но это еще не все. Дело в том, что все современные видеокарты содержат специализированные GPU -
векторные и пиксельные процессоры, используемые для ускорения операций преобразования
вершин и закраски примитивов. Так как эти процессоры принимают участие при
визуализации любых примитивов, приложение должно запрограммировать их на выполнения
требуемых преобразований. Если этого не сделать, то результат вызова метода device.DrawUserPrimitives() будет непредсказуемым. Отсюда возникает
необходимость программирования вершинных и пиксельных процессоров графического
ускорителя видеокарты.
Для программирования вершинных и пиксельных процессоров GPU служит
язык HLSL (High Level
Язык HLSL хотя и тесно связан с архитектурой графического процессора, но является управляемым C -подобным языком программирования для некоторого виртуального процессора, приближенного к некоторому реальному прототипу. Такой подход напоминает применение промежуточного языка IL в .NET. Компиляция шейдера в систему команд физического процессора происходит непосредственно перед загрузкой шейдера в GPU. Это позволяет несколько абстрагироваться от аппаратной части процессора, генерируя при компиляции промежуточный байт-код, и таким образом расширить семейство видеокарт, на которых может работать графическое приложение.
На первой ступени вершины обрабатываются вершинным процессором по программе, называемой вершинным шейдером. На выходе из вершинного процессора получаются так называемые трансформированные (преобразованные) вершины. К вершинам могут быть "привязаны" различные параметры: цвет вершины, текстурные координаты и так далее. Координаты трансформированных вершин задаются в логической системе однородных координат, называемой clip space.
Однородные координаты вершины определяются четырьмя числами: (x, y, z, w).
Перевод однородных координат в обычные геометрические осуществляется путем
деления первых трех компонентов на четвертый компонент w: (x/w, y/w, z/w).
Например, вершине с однородными координатами (1, 2, 3, 4) в трехмерном пространстве
соответствует точка с координатами (1/4, 2/4, 3/4). Использование четвертого
компонента обусловлено рядом особенностей алгоритмов визуализации трехмерных
изображений, используемых в 3D -графике.
При визуализации двухмерных изображений компонент w обычно полагают равным
1. В этом случае нижнему левому углу клиентской области формы соответствует
точка с координатами (-1, -1, 0, 1), правому верхнему углу клиентской области
- (1, 1, 0, 1), а центру клиентской области - соответственно (0, 0, 0, 1).

На второй ступени графического конвейера видеокарта производит преобразование
координат вершины из логической системы координат в оконную. По умолчанию координаты
трансформируются таким образом, чтобы растянуть изображение на всю поверхность
элемента управления. Managed DirectX через код XNA позволяет
программисту задавать координаты вершин в оконных координатах. В этом случае,
при вызове метода device.DrawUserPrimitives() вершины сразу поступают
на третью стадию графического конвейера, минуя первую и вторую стадии.
На третьей ступени идет сборка примитивов. На этой стадии вершины
объединяются в примитивы. Тип примитивов определяется первым параметром метода device.DrawUserPrimitives(). Так при использовании параметра PrimitiveType.TriangleStrip вершины
трактуются, как опорные точки полосы треугольников. При этом каждый треугольник
из полосы является независимым примитивов и обрабатывается независимо от других
треугольников этой полосы.
На четвертой ступени происходит растеризация примитивов – преобразование каждого примитива в набор пикселей экрана. Параметры внутренних пикселей примитива (например, цвет) определяются путем интерполяции соответствующих параметров вершин вдоль поверхности примитива. Благодаря этой интерполяции при закраске треугольника с разноцветными вершинами образуются красивые цветовые переходы - градиенты цвета.
На пятой ступени происходит обработка пикселей пиксельным процессором с использованием программы, называемой пиксельным шейдером. На вход пиксельному процессору подаются параметры пикселя (цвет, текстурные координаты и т.д.), полученные путем интерполяции соответствующих вершинных параметров вдоль поверхности примитива. После обработки входных параметров, пиксельный процессор возвращает цвет пиксела, который заносится в кадровый буфер. При этом возможно выполнение некоторой простой дополнительной обработки изображения вроде смешивания цветов при эффекте полупрозрачности.
Самой крупной логической единицей HLSL является эффект ( Effect ),
хранящийся в отдельном текстовом файле с расширением .fx. В принципе,
эффект можно считать аналогом материала в 3DS MAX. Каждый эффект состоит
из одной или нескольких техник ( technique ). Техника – это способ
визуализации материала. Например, эффект визуализации мраморного материала
может содержать три техники: технику High для получения изображения
наивысшего качества при низкой производительности, Medium для получения
изображения среднего качества, и Low – максимальная производительность
при низком качестве изображения.
Каждой технике сопоставлен пиксельный и вершинный шейдер, при этом несколько техник могут использовать общий шейдер. Количество техник и их названия могут быть произвольными.
Известным инструментом для разработки шейдеров является RenderMonkey компании AMD. Этот продукт можно скачать по адресу (http://developer.amd.com/gpu/rendermonkey/Pages/default.aspx).
В HLSL все встроенные типы делятся на две большие группы: скалярные и векторные. Скалярные типы данных являются аналогами встроенных типов данных языка C.
| Тип | Описание |
|---|---|
bool |
Логический тип, который может принимать значения true или false |
int |
32-х битное целое число |
half |
16-ти битное число с плавающей точкой |
float |
32-х битное число с плавающей точкой |
double |
64-х битное число с плавающей точкой |
Задавая тип скаларной переменной, мы просто указываем компилятору свое пожелание.
Если текущий ускоритель не поддерживает некоторые типы данных, используемые
в программе, то при компиляции шейдера в машинный код они будут заменены ближайшими
аналогами. Например, тип double может быть заменен на тип float, half или
какой-нибудь иной внутренний тип. Поэтому программист должен стараться избегать
жесткой привязки к точности и допустимому диапазону значений используемого
типа данных. Особенно это актуально для типа int, так как подавляющее
большинство современных ускорителей не поддерживают тип int, в результате
чего он эмулируется посредством одного из вещественных типов. Подобная эмуляция
приводит к существенному падению производительности шейдера.
Большинство данных, используемых в трехмерной графике, является векторами
размерности от 2 до 4 скаларных переменных. Так, координаты точки в трехмерном
пространстве задаются трехмерным вектором, цвет пиксела - четырехмерным вектором
(три цвета и альфа-канал). Соответственно, все современные GPU являются
Для описание векторных данных в HLSL имеются векторные типы размерностью
от 2 до 4. Если сказать обобщенно, то векторный тип из N элементов скалярного
типа type задается с использованием синтаксиса, отдаленно напоминающего
шаблоны из C#:
vector<type, size>
type - имя скалярного типа: bool, int, half, float или double size - размерность вектора: 1, 2, 3 или 4Например, объявление переменной v, являющейся вектором из четырех чисел
типа float, выполняется так:
vector<float, 4> v;
Но на практике обычно используется сокращенная запись
float4 v;
Язык HLSL позволяет инициализировать вектор двумя способами. Первый
способ - перечислить значения вектора в фигурных скобках, например, присвоение
четырехмерному вектору v начального значения выглядит так
float4 v = {0.2, 0.4, 0.6, 0.8};
Другой способ - создать новый вектор с использованием конструктора и присвоить
его вектору v:
float4 v = float4(0.2, 0.4, 0.6, 0.8);
Любой вектор имеет множество конструкторов, которые могут принимать в качестве
параметров как переменные скалярных типов, так и - векторных. Единственное
ограничение: общее количество всех компонентов векторных и скалярных типов
должно быть равно N. Подобное многообразие конструкторов дает программисту
большую гибкость при инициализации векторов. Например, вот как последовательно
можно создать векторы разных размерностей:
float2 a = {0.1, 0.2}; // Создаем двухмерный вектор
float2 b = float2(0.3, 0.4); // Создаем еще один двухмерный
float3 c = float3(b, 1.0); // Создаем трехмерный вектор
float4 d = float4(0.7, c); // Создаем четырехмерный вектор
float4 e = float4(a, b); // Создаем еще один четырехмерный вектор
Данные в 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
}
Мы получили функции для вершинного и пиксельного процессора - вершинный и
пиксельный шейдеры. Заключительный этап написания эффекта - создание
техники (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 {используемый профиль} {пиксельный шейдер};
Профиль шейдера (vs_1_1, Pixel
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 |
Большинство видеокарт поддерживает несколько профилей вершинных и пиксельных шейдеров. В результате каждый разработчик сталкивается с проблемой выбора используемого профиля. Чаще всего выбор версии шейдеров определяется минимальными требованиями к приложению.
Допустим, необходимо, чтобы наша программа могла работать на видеокартах класса 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.Noneplatform – значение перечислимого типа 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();
}
Теперь создадим обработчики нужных событий и заполним их кодом.
Визуализация примитивов, использующих эффект, начинается с вызова метода effect.Begin() объекта
эффекта. Далее приложение должно перебрать все проходы
текущей техники (коллекция effect.CurrentTechnique.Passes ) и вызвать метод pass.Begin() для каждого прохода. Внутри прохода нужно визуализировать
примитивы с использованием метода device.DrawUserPrimitives(). Визуализацию
прохода нужно закончить вызовом метода pass.End(). По окончанию визуализации
всего эффекта приложение должно вызвать метод effect.End().
Через контекстное меню
перейдите в режим View Designer формы, выделите на ней объект xnaPanel1 и
откройте панель Properties в
режиме Events
Найдите событие Paint объекта xnaPanel1 и
двойным щелчком на его поле справа создайте обработчик, который заполните
такprivate void xnaPanel1_Paint(object sender, PaintEventArgs e)
{
if (closing)
return;
try
{
if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.Lost)
throw new DeviceLostException();
if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.NotReset)
device.Reset(presentParams);
// Очищаем область экрана
device.Clear(XNAGraphics.Color.CornflowerBlue);
device.RenderState.CullMode = CullMode.None;
device.VertexDeclaration = decl;
// Задаем координаты вершин из установленного в группе панелей цвета
vertices[0] = new VertexPositionColor(new Vector3(0.0f, 0.8f, 0.0f), new XNAGraphics.Color(
vertex1Color.BackColor.R, vertex1Color.BackColor.G, vertex1Color.BackColor.B));
vertices[1] = new VertexPositionColor(new Vector3(0.8f, -0.8f, 0.0f), new XNAGraphics.Color(
vertex2Color.BackColor.R, vertex2Color.BackColor.G, vertex2Color.BackColor.B));
vertices[2] = new VertexPositionColor(new Vector3(-0.8f, -0.8f, 0.0f), new XNAGraphics.Color(
vertex3Color.BackColor.R, vertex3Color.BackColor.G, vertex3Color.BackColor.B));
effect.Begin();
foreach(EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
pass.End();
}
effect.End();
device.Present();
}
catch (DeviceNotResetException)
{
Invalidate();
}
catch (DeviceLostException)
{
closing = true; // Больше не выполнять OnPaint()
string title = "Сбой графического устройства";
string message = "Работа программы будет завершена.\n"
+ "Закройте все ненужные программы\n"
+ "и повторите запуск этого приложения";
MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Stop);
Application.Idle += new EventHandler(Application_Idle);
return;
}
catch (Exception ext) // Все другие исключения
{
closing = true;
MessageBox.Show(ext.Message);
Application.Idle += Application_Idle;
}
}
Выделите на форме объект xnaPanel1 и
через панель Properties в режиме Events создайте обработчик
события Resize, который заполните такprivate void xnaPanel1_Resize(object sender, EventArgs e)
{
// Устанавливаем размеры заднего буфера по клиентской области панели
presentParams.BackBufferWidth = xnaPanel1.ClientSize.Width;
presentParams.BackBufferHeight = xnaPanel1.ClientSize.Height;
// Сбрасываем устройство и применяем к нему новые параметры
// Если окно не минимизировано
if (this.WindowState != FormWindowState.Minimized)
device.Reset(presentParams);
}
Выделите саму форму
щелчком на ее заголовке и
через панель Properties в режиме Events создайте обработчик
события FormClosed, который заполните такprivate void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
// Удаляем (освобождаем) устройство
device.Dispose();
// На всякий случай присваиваем ссылке на устройство значение null
device = null;
}
Осторожно (чтобы не
создать обработчик) выделите все три кнопки на форме (удерживая клавишу Ctrl ) и через панель Properties в
режиме Events создайте
обработчик события Click с именем vertexColor_Click, который заполните
такprivate void vertexColor_Click(object sender, EventArgs e)
{
Button button = (Button)sender;
colorDialog1.Color = button.BackColor;
if (colorDialog1.ShowDialog() == DialogResult.OK)
{
button.BackColor = colorDialog1.Color;
xnaPanel1.Invalidate();
}
}
В панели Solution
Explorer выделите узел проекта Application2 и через контекстное меню сделайте проект
стартовым. Запустите приложение - должен получиться такой результат
ColorFill.fx из
прилагаемой к работе папки Source, не редактируя его. Незабудьте в этом
случае изменить свойство файла Copy to Output Directory на
значение Copy if newer.Если вы все делали так как описано, то исполнимые сборки для упражнений 1
и 2 должны находиться относительно
папки решений FormsXNA в подпапках:
FormsXNA\Application1\bin\Debug\Application1.exe FormsXNA\Application2\bin\Debug\Application2.exe
В этом разделе, для тренировки, мы добавим к проекту простое приложение, запускающее сборки с выполненными упражнениями, чтобы легче было их демонстрировать.
Добавьте к решению
новый проект с именем StartForm и сделайте его стартовым
Переименуйте файл формы Form1.cs в StartForm.cs и откройте
его в режиме View Designer и поместите на форму
два экземпляра компонента LinkLabel
Создайте для них
обработчики события LinkClicked и заполните их следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace StartApp
{
public partial class StartForm : Form
{
System.Diagnostics.Process exe = new System.Diagnostics.Process();
String path; // Путь к папке с решением
public StartForm()
{
InitializeComponent();
path = Directory.GetCurrentDirectory();// Получаем путь к сборке StartForm
int pos = path.LastIndexOf(@"\bin\");
path = path.Substring(0, pos);// Убираем 2 последних секции пути
pos = path.LastIndexOf(@"\");
path = path.Substring(0, pos);// Убираем еще одну секцию
}
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Directory.SetCurrentDirectory(path + @"\Application1\bin\Debug");// Путь к сборке
exe.StartInfo.FileName = "Application1.exe";
System.Diagnostics.Debug.WriteLine(path); // Для отладки в окно Output
exe.Start();
}
private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Directory.SetCurrentDirectory(path + @"\Application2\bin\Debug");// Путь к сборке
exe.StartInfo.FileName = "Application2.exe";
System.Diagnostics.Debug.WriteLine(path); // Для отладки в окно Output
exe.Start();
}
}
}
Код будет работать при условии, что сборка стартовой формы находится в своем
каталоге bin дерева решения.

Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.