Пример программы
Ниже мы подробно рассмотрим программу, запускающую сложение матриц на графическом процессоре. Текст программы написан на языке C#. Мы не будем исчерпывающе комментировать синтаксис этого языка, но детально прокомментируем операции, исполняемые над данными для видеокарты. · Подключение необходимых библиотек. Используется стандартная библиотека System, а кроме того – библиотека Microsoft.Xna.Framework, упрощающая подготовку данных для графического процессора. Раздел этой библиотеки 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 являются стандартными для проектов на C#. Внутри процедуры 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 задаёт размеры матриц (3´ 3), которые мы будем складывать. Теперь создается рендер-цель – массив 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, в объектах которого, кроме самих массивов, хранятся дополнительные параметры. Как и выше, значения этих параметров устанавливаются при создании новой переменной класса, строкой new Texture2D(gpu, size, size, 1, ResourceUsage.Dynamic, SurfaceFormat.Single, ResourceManagementMode.Manual), значениями внутри скобок. */ Texture2D tex_matrix1 = new Texture2D(gpu, size, size, 1, ResourceUsage.Dynamic, SurfaceFormat.Single, ResourceManagementMode.Manual); Texture2D tex_matrix2 = new Texture2D(gpu, size, size, 1, ResourceUsage.Dynamic, SurfaceFormat.Single, ResourceManagementMode.Manual); · Заполнение массивов исходных данных конкретными значениями /* Теперь, задаём конкретные значения элементов исходных матриц. Сначала эти значения записываются в одномерные массивы matrix1 и matrix2 размеров size * size. Элементы a ij и b ij после преобразования матриц к одномерным массивам будут иметь номера j * size + i. Затем, мы копируем созданные массивы в текстуры класса Texture2D tex_matrix1 и tex_matrix2 операциями tex_matrix1.SetData< float> (matrix1) и tex_matrix2.SetData< float> (matrix2). */ float[] matrix1 = new float[size * size], matrix2 = new float[size * size]; for (int j = 0; j < size; j++) for (int i = 0; i < size; i++) { matrix1[j * size + i] = j * size + i; matrix2[j * size + i] = i * size + j; } tex_matrix1.SetData< float> (matrix1); tex_matrix2.SetData< float> (matrix2); · Установление соответствия между целочисленными индексами элементов матриц и текстурными координатами этих элементов /*Следующая задача – установление соответствия между целочисленными индексами элементов матриц 3´ 3 и текстурными координатами этих же элементов внутри квадрата (-1; -1)-(1; 1), или quad’а, которыми будет оперировать графический процессор, а также внутри квадрата (0; 0)-(1; 1), в который отображается массив результатов (рендер-цель). Для этого достаточно задать соответствие между индексами «крайних» элементов матриц и координатами вершин квадрата. Соответствие это показано в таблице 5.1. При необходимости рассмотрения матриц других размеров, знаменатель 6 нужно заменить на значение 2*size. Таблица 5.1 Соответствие между координатами вершин quad’а (-1; -1)-(1; 1) и координатами элементов складываемых матриц
Метод определения координат элементов проиллюстрирован также на рис. 5.2. Видно, что приведённые формулы действительно обеспечивают попадание элементов массива 3´ 3 в необходимые области. Учтён также тот факт, что ось Y на экране (и в рендер цели) направлена сверху вниз. Третья пространстваенная координата во входных данных у нас не используется, поэтому в конструкторах вида Vector3(-1, 1, 0) третья координата равна нулю, тогда как первые два значения задают координаты вершин quad’а */ float dx = 0.5f / size, dy = 0.5f / size; // смещения для адресации центров текселей 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’а. Этих вершин 4, чем и обусловлен размер 4 * VertexPositionTexture.SizeInBytes */ VertexBuffer quad = new VertexBuffer(gpu, 4 * VertexPositionTexture.SizeInBytes, ResourceUsage.None, ResourceManagementMode.Automatic); quad.SetData< VertexPositionTexture> (v); VertexDeclaration quadVertexDeclaration = new VertexDeclaration(gpu, VertexPositionTexture.VertexElements); · Компилирование эффектов, содержащих шейдеры /* В библиотеку Microsoft.Xna.Framework встроен компилятор текстовых файлов, содержащих шейдеры для графического процессора, таких как sum.fx (начало примера и Приложение 1). Следующая строка компилирует файл sum.fx и записывает полученную программу (в машинных кодах) в область оперативной памяти, на которую указывает переменная e типа CompiledEffect. */ CompiledEffect e = Effect.CompileEffectFromFile(" sum.fx", null, null, CompilerOptions.None, TargetPlatform.Windows); /* Проверка успешности компиляции */ if (! e.Success) throw new Exception(e.ErrorsAndWarnings); · Выбор шейдера, который будет исполняться /* Создаётся новая переменная, описывающая эффект, который будет исполняться на графическом процессоре. В качестве кода эффекта выбирается откомпилированная программа, на которую указывает переменная e. */ Effect fx = new Effect(gpu, e.GetEffectCode(), CompilerOptions.None, null); /* В качестве запускаемой на видеокарте процедуры выбирается техника sum из файла sum.fx (описана выше) */ fx.CurrentTechnique = fx.Techniques[" sum" ]; · Копирование исходных данных в видеопамять, доступную графическому процессору /* В качестве исходных данных передаём эффекту fx текстуры с исходными данными tex_matrix1 и tex_matrix2, которые мы уже создали в начале программы: */ fx.Parameters[" tex_matrix1" ].SetValue(tex_matrix1); fx.Parameters[" tex_matrix2" ].SetValue(tex_matrix2); /* Задаем выходные параметры: quad и рендер-цель: */ gpu.Vertices[0].SetSource(quad, 0, VertexPositionTexture.SizeInBytes); gpu.VertexDeclaration = quadVertexDeclaration; gpu.SetRenderTarget(0, tex_target); · Исполнение шейдера на графическом процессоре fx.Begin(); fx.CurrentTechnique.Passes[" sum" ].Begin(); gpu.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2); fx.CurrentTechnique.Passes[" sum" ].End(); fx.End(); · Получение результата из рендер-цели и сохранение его в текстовом файле /* Копирование массива результатов из видеопамяти в оперативную память, доступную центральному процессору */ gpu.ResolveRenderTarget(0); /* Создаём текстовый файл для записи результата */ using (System.IO.StreamWriter w = new System.IO.StreamWriter(" results.txt")) { /* Создаём одномерный массив, в который будут скопированы данные из рендер-цели, для дальнейшей обработки центральным процессором */ float[] matrix3 = new float[size * size]; /* Копирование данных из рендер цели в массив matrix3. Аргумент < float> означает, что данные будут скопированы в виде скалярных чисел одинарной точности */ tex_target.GetTexture().GetData< float> (matrix3); /* Запись данных из массива matrix3 в текстовый файл */ for (int j = 0; j < size; j++) { for (int i = 0; i < size; i++) w.Write(" \t{0}", matrix3[j * size + i]); w.WriteLine(); } }}}
|