
- •Содержание
- •Очистка буферов графического устройства
- •Избирательная очистка буферов
- •Устранение скрытых дефектов приложения
- •Общие сведения о примитивах
- •Введение в hlsl
- •Типы данных hlsl
- •Функции и семантики hlsl
- •Техники, проходы и профили hlsl
- •IncludeHandler – объект, используемый для обработки директив #include в fx-файле. Так как наш файл не содержит директив #include, мы будем использовать значение null
- •Визуализация объекта, использующего эффект
- •Запускающее приложение
Устранение скрытых дефектов приложения
Попробуйте поизменять размеры окна и отметьте, что программа отображает доску непонятным образом как при уменьшении, так и при увеличении окна. Устраним сначала проблему, возникающую при уменьшении окна.
Начнем с того, что при уменьшении окна необходимости в дорисовке экрана нет и событие Paint наследуемого формой класса Control не срабатывает. В принципе, для борьбы с этим недоразумением мы могли бы перекрыть метод диспетчеризации OnResize() формы (или подписаться на событие Resize ) и вставить туда вызов метода Invalidate(), принудительно генерирующего событие Paint. Однако существует гораздо более элегантное решение: если установить у формы стиль ResizeRedraw, то при изменении размера формы будет автоматически генерироваться событиеPaint.
Добавьте в обработчик события Load установку стиля формы ResizeRedraw
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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
Запустите приложение и поизменяйте размеры окна
Теперь экран при уменьшении размеров стал перерисовываться. Но здесь стала очевидной другая проблема: при изменении размеров появились заметные мигания окна. Чтобы эти мигания стали еще заметнее, выполните следующее:
В рабочей области редактора кода вызовите контекстное меню и выполните команду View Designer (или просто выполните команду меню оболочки View/Designer )
Выделите форму щелчком мыши и в панели Properties установите свойство BackColor формы в значение Green
Запустите приложение и поизменяйте размеры окна - сейчас мигания зеленого цвета на фоне шахматной доски стали более заметны
Дело здесь в том, что перед каждым вызовом события Paint автоматически вызывается виртуальный метод OnPaintBackground() наследуемого формой класса Control, который очищает экран цветом BackColor. Эта функциональность позволяет разработчику, использующему обычный GDI+, не заботится об очистке экрана, однако в нашем случае такая "самовольная" очистка формы стандартными средствами Windows приводит лишь к мерцанию. Следовательно, нам необходимо запретить форме закрашивать экран перед вызовом обработчика события Paint. Это делается установкой для формы стиля Opaque.
Добавьте в обработчик события Load установку стиля формы Opaque
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);
// Перерисовывать форму при изменении размеров
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Запретить очистку экрана средствами GDI+
this.SetStyle(ControlStyles.Opaque, true);
}
Запустите приложение - теперь мигания формы при изменении ее размеров исчезли
И так, первую проблему мы решили, но осталась вторая, гораздо более неприятная - некорректное масштабирование шахматной доски при изменении размера окна. Дело в том, что при создании графического устройства мы задаeм размер вспомогательного буфера, используемого при двойной буферизации, равным размеру клиентской области окна и к тому же только один раз (в обработчике события Load формы). Когда мы изменяем размер окна, его клиентская область так же изменяется. А вот размер вспомогательного буфера графического устройства остаeтся неизменным. Получается, что при изменении размеров окна происходит рассинхронизация между размерами клиентской области окна просмотра и вспомогательного буфера-источника, в котором рисуется изображение. В результате, приложение начинает работать некорректно.
Чтобы синхронно подстраивать параметры графического устройства под новые размеры окна, нужно перекрыть метод диспетчеризации OnResize()формы, включить в него новые настройки объекта presentParams и перезапустить объект device его методом Reset(). Но прежде всего нужно ссылку на объект presentParams сделать видимой в методе диспетчеризации OnResize(), т.е. вынести ее объявление в поле класса.
На основе вышесказанного внесите следующие изменения в класс MainForm
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;
// Объявим объект настроек графического устройства
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:
При восстановимой потере устройства приложение выдает исключениеMicrosoft.Xna.Framework.Graphics.DeviceNotResetException, а при невосстановимой -Microsoft.Xna.Framework.Graphics.DeviceLostException. Если сгенерировано исключениеDeviceNotResetException, то приложение должно восстановить устройство методом device.Reset() и снова перерисовать изображение путeм вызова метода Invalidate(), в противном случае просто не реагировать на событие 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(); // Завершает работу приложения (приведено для разнообразия) }
В этом случае будет выдано окно с сообщением
Упражнение 2. Вывод графической информации XNA на пользовательский элемент управления В упражнении 1 наше приложение осуществляло монопольный вывод графической информации на всю поверхность формы. Такой подход не позволяет размещать на форме элементы управления. Можно попробовать использовать вспомогательные диалоговые окна или плавающие панели инструментов, но это не очень красивое решение проблемы. Гораздо интереснее было бы научиться выводить информацию не на саму форму, а на элементы управления, размещенные на ней (к примеру, на элемент управления Panel ). Это позволило бы на освободившемся пространстве размещать другие нужные элементы пользовательского интерфейса. Все элементы управления наследуют от класса Control, в том числе класс Form и Panel. Ранее при управлении экземпляром класса Form мы фактически использовали методы и свойства класса Control. В данном упражнении мы с таким же успехом можем управлять экземпляром классаPanel, помещенным на форму. Единственная сложность состоит в том, что при управлении экземпляром Form наш код размещался внутри его расширения - классе MainForm, и имел доступ ко всем общедоступным ( public ) и защищенным ( protected ) свойствам и методам, унаследованным, в конечном итоге, от класса Control. А если попытаться просто управлять экземпляром класса Panel, помещенным на форму, то видимыми из расширения класса Form в объекте Panel (и, соответственно, в Control ) будут только общедоступные члены. Чтобы получить полный доступ к нужным сервисам класса Control, нужно создать расширение класса Panel и упаковать в нем вызовы защищенных членов в общедоступные. Фактически, нам нужно создать новый элемент управления, которые называют пользовательскими (в отличии от стандартных библиотечных). Создание заготовки пользовательского элемента управления
увеличить изображение
using System.Drawing;
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); } } }
Мы создали библиотечный пользовательский элемент управления для вывода графической информации XNA, который теперь можно размещать на форме.
увеличить изображение После того, как мастер создаст новый проект и выведет визуальное представление формы в рабочую область, можно убедиться, что в панели инструментов Toolbox появилась новая вкладка XNAPanel Components с пиктограммой компонета XNAPanel.
В результате форма в режиме проектирования примет следующий вид
Средствами XNA мы нарисуем примитивный треугольник на экземпляре нашего компонента XNAPanel, цвет вершин которого мы будем выбирать из диалоговой панели. Для этого, как и ранее, нужно добавить в класс формы несколько вспомогательных полей, создать обработчики событий Load,FormClosed, Paint и т.д. и заполнить их соответствующим кодом. Но прежде, чем приступить к программированию обработчиков, познакомимся с новой техникой, которую мы будем использовать в этом упражнении для управления видеокартой на низком уровне. |
|