Структура программы для центрального процессора

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

В настоящем примере программа для центрального процессора написана на языке С#, она приведена ниже. Поскольку подробное рассмотрение синтаксиса C# выходит за рамки настоящего пособия, детально прокомментированы только операции, непосредственно касающиеся графического процессора.

В качестве интерфейса (API), обеспечивающего взаимодействие пользовательского приложения с графическим процессором, мы используем библиотеку Microsoft DirectX с дополнительной библиотекой Microsoft XNA Game Studio Express 2.0, упрощающей программирование.

Последовательность операций, выполняемых центральным процессором при взаимодействии с GPU

Рис. 2.8. Последовательность операций, выполняемых центральным процессором при взаимодействии с GPU

Реализация программы для центрального процессора на С#

Ниже приведен полный текст программы для CPU, организующей взаимодействие с графическим процессором. Программа написана на языке C# из состава Microsoft Visual Studio 2005. Используется библиотека Microsoft XNA Game Studio Express 2.0. Комментарии, вставленные в текст без использования символов “//” или не соответствуют синтаксису С#, но если их удалить из текста, то программа будет работать.

Подключение необходимых библиотек

Программой используются стандартная библиотека System, а также библиотека Microsoft.Xna. Framework, которая реализует среду XN А Game Studio и осуществляет взаимодействие с DirectX. Раздел этой библиотеки Microsoft.Xna. Framework.Graphics подключаем отдельно, чтобы сократить в тексте программы описание доступа к ресурсам этого раздела.

using System;

using Microsoft.Xna. Framework;

using Microsoft.Xna.Framework.Graphics;

public class GPGPU: Microsoft.Xna.Framework.Game

{

//Описание переменных, используемых программой /* Задается размер матрицы, количество элементов в строке/столб- це */

int size = 3;

/* Описываем переменную graphics класса GraphicsDeviceManager и затем инициализируем эту переменную операцией graphics = new GraphicsDeviceManager (this). Эта переменная будет обеспечивать доступ к процедурам для управления графическим процессором из библиотеки Microsoft.Xna. Framework */

GraphicsDeviceManager graphics;

//Стандартные процедуры, реализующие исполнение программы на C#

/* Ниже описаны процедуры, которые будут последовательно исполнены сразу после запуска программы. Процедуры Main и Initialize являются стандартными для проектов на С#. Внутри процедуры Initialize находится вызов процедуры MatrixSum (), которая и осуществляет суммирование матриц на графическом процессоре. */

static void Main (string [] args) {using (GPGPU game = new GPGPU ()) game.Run ();}

public GPGPU () {graphics = new GraphicsDeviceManager (this);} protected override void Initialize ()

{

base. Initialize ();

MatrixSum ();

Exit ();

}

//Процедура, суммирующая матрицы с использованием GPU private void MatrixSum ()

{

/* Выше была введена переменная graphics класса GraphicsDeviceManager. В этом классе есть нужный нам подкласс GraphicsDevice. Создаем переменную gpu этого подкласса */

GraphicsDevice gpu = graphics.GraphicsDevice;

//Выделение памяти для рендер-цели

/* Созданная в начале программы переменная size = 3 задает размеры матриц (3x3), которые мы будем складывать. Теперь создается рендер-цель — массив 3 х 3, в который будет выводиться результат расчетов. Соответствующая переменная называется tex_target. Она является не простым массивом 3 х 3, а принадлежит к классу RenderTarget2D из библиотеки Microsoft.Xna.Framework. Кроме самого массива в объектах этого класса хранятся некоторые дополнительные параметры, в частности, указатель на переменную gpu, который задает конкретную видеокарту, для которой создается рендер-цель. */

RenderTarget2D tex_target = new RenderTarget2D (gpu, size, size, 1, SurfaceFormat. Single);

//Выделение памяти для текстур с исходными данными /* Создаются текстуры — массивы размерами size*size, — в которых будут храниться исходные матрицы. Как и рендер-цель, соответствующие переменные не являются простыми массивами, а принадлежат к более сложному классу Texture2D, в объектах которого, кроме самих массивов, хранятся дополнительные параметры. Как и выше, при создании новой переменной класса, значения этих параметров устанавливаются строкой newTexture2D (gpu, size, size, 1, Resource Usage. Dynamic, SurfaceFormat.Single, ResourceManagementMode.Manual), значениями внутри скобок. */

Texture2D tex_matrixl = newTexture2D (gpu, size, size, 1, ResourceUsage. Dynamic, SurfaceFormat.Single, ResourceManagementMode.Manual);

Texture2D texmatrix2 = newTexture2D (gpu, size, size, 1, ResourceUsage. Dynamic, SurfaceFormat.Single, ResourceManagementMode.Manual);

//Заполнение массивов исходных данных конкретными значениями

/* Теперь задаем конкретные значения элементов исходных матриц. Сначала эти значения записываются в одномерные массивы matrix 1 и matrix2 размерами size * size. Элементы а9 и b9 после преобразования матриц к одномерным массивам будут иметь номера j * size + i. Затем мы копируем созданные массивы в текстуры класса Texture2D tex_matrixl и tex_matrix2 с помощью операции tex matrixl. SetData (matrixl) и tex_matrix2.SetData (matrix2). */ float [] matrixl = new float [size * size], matrix2 = new float [size * size]; for (intj = 0; j < size; j++) for (int i = 0; i < size; i++) { matrixl [j * size + i] = j * size + i; matrix2 [j * size + i] = i * size + j;

}

tex_matrixl.SetData (matrixl); tex_matrix2.SetData (matrix2);

/*Следующая задача — установление соответствия между целочисленными индексами элементов матриц 3x3 и текстурными координатами этих же элементов внутри квадрата (-1;-1)- (1; 1), или quad’a, которыми будет оперировать графический процессор, а также внутри квадрата (0;0)-(1;1), в который отображается массив результатов (рендер-цель). Для этого достаточно задать соответствие между индексами «крайних» элементов матриц и координатами вершин квадрата. Соответствие это показано ниже. При необходимости рассмотрения матриц других размеров, знаменатель 6 нужно заменить на значение 2*size.

Соответствие между координатами вершин quad’a (-1;-1)-(1;1)и координатами элементов складываемых матриц:

  • (-1; 1)..............(1/6; 1/6)
  • (—1; —1)............(1/6; 1+1/6)
  • (1; 1).................(1+1/6; 1/6)
  • (1; —1)..............(1+1/6; 1+1/6)

Метод определения координат элементов проиллюстрирован также на рис. 2.4. Видно, что приведенные формулы действительно обеспечивают попадание элементов массива 3 х 3 в необходимые области. Учтен также тот факт, что ось Y на экране (и в рендер-цели) направлена сверху вниз.

Третья пространственная координата во входных данных у нас не используется, поэтому в конструкторах вида Vector3 (-1, 1, 0) третья координата равна нулю, тогда как первые два значения задают координаты вершин quad’a */

float dx = 0.5f/size, dy = 0.5f/size;//cMemeHHH для адресации центров текселей

VertexPositionTexture [] v = new VertexPositionTexture [] { new VertexPositionTexture (new Vector3 (—1, 1, 0), new Vector2 (0 + dx, 0 + dy)),

new VertexPositionTexture (new Vector3 (—1,-1, 0), new Vector2 (0 + dx, 1 + dy)), new VertexPositionTexture (new Vector3 (1,1,0), new Vector2 (1 + dx, 0 + dy»,

new VertexPositionTexture (new Vector3 (1,-1, 0), new Vector2 (1 + dx, 1 + dy))

}

/* Следующей стандартной строкой выделяется область памяти для хранения координат вешин quad’a. Этих вершин 4, чем и обусловлен размер 4 * VertexPositionTexture.SizelnBytes */

VertexBuffer quad = new VertexBuffer (gpu, 4 * VertexPositionTexture. SizelnBytes, ResourceUsage.None, ResourceManagementMode.Automatic); quad.SetData (v);

VertexDeclaration quadVertexDecIaration = new VertexDecIaration (gpu, VertexPositionTexture.VertexElements);

//Компилирование эффектов, содержащих шейдеры /* В библиотеку Microsoft.Xna. Framework встроен компилятор текстовых файлов, содержащих шейдеры для графического процессора, таких как sum.fx (см. начало примера и прил. 1). Следующая строка компилирует файл sum.fx и записывает полученную программу (в машинных кодах) в область оперативной памяти, на которую указывает переменная е типа CompiledEffect. */

Compiled Effect е = Effect.CompileEffectFromFile (“sum.fx”, null, null, CompilerOptions.None, TargetPlatform.Windows);

/* Проверка успешности компиляции */

if (! e.Success) throw new Exception (e.ErrorsAndWarnings);

//Выбор шейдера, который будет исполняться /* Создается новая переменная, описывающая эффект, который будет исполняться на графическом процессоре. В качестве кода эффекта выбирается откомпилированная программа, на которую указывает переменная е. */

Effect fx = new Effect (gpu, e.GetEffectCode (), CompilerOptions.None, null);

/* В качестве запускаемой на видеокарте процедуры выбирается техника sum из файла sum.fx (описана выше) */ fx.CurrentTechnique = fx.Techniques [“sum”];

/* Копирование исходных данных в видеопамять, доступную графическому процессору. В качестве исходных данных передаем эффекту fix, текстуры с исходными данными tex matrixl и tex_matrix2, которые мы уже создали в начале программы: */

fx.Parameters [“tex_matrixl”].SetValue (tex matrixl); fx.Parameters [“tex_matrix2”].SetValue (tex_matrix2);

/* Задаем выходные параметры: quad и рендер-цель: */ gpu.Vertices [0].SetSource (quad, 0, VertexPositionTexture.SizelnBytes); gpu.VertexDeclaration = quadVertexDeclaration; gpu.SetRenderTarget (0, tex_target);

//Исполнение шейдера на графическом процессоре fx.Begin 0;

fx.CurrentTechnique.Passes [“sum”].Begin (); gpu.DrawPrimitives (PrimitiveType.TriangleStrip, 0, 2); fx.CurrentTechnique.Passes [“sum”].End (); fx.End 0;

/* Получение результата из рендер-цели и сохранение его в текстовом файле

Копирование массива результатов из видеопамяти в оперативную память, доступную центральному процессору */ gpu.ResolveRenderTarget (0);

/* Создаем текстовый файл для записи результата */ using (System.IO. StreamWriter w = new System.IO. StreamWriter (“results.txt”))

{

/* Создаем одномерный массив, в который будут скопированы данные из рендер-цели, для дальнейшей обработки центральным процессором */ float [] matrix3 = new float [size * size];

/* Копирование данных из рендер-цели в массив matrix3. Аргумент означает, что данные будут скопированы в виде скалярных чисел одинарной точности */

tex_target.GetTexture ().GetData (matrix3);

/* Запись данных из массива matrix3 в текстовый файл */ for (int j = 0; j < size; j++)

{for (int i = 0; i < size; i++) w.Write (“ {0}”, matrix3 [j * size + i]); w.WriteLine ();}}}}

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

 
Посмотреть оригинал
< Пред   СОДЕРЖАНИЕ   ОРИГИНАЛ   След >