В посте описывается мой вариант процесса программирования видео карт и моя библиотека для использования ATI StreamComputing из .NET языков (в примере C#). По сути это оболочка над ранее написанной библиотекой useGPU. Логика работы с видео картой и процесса разработки приложения осталась прежней. Объясню на примере.
Шаг 1. Написание шейдера.
Вот шейдер написанный на HLSL. Каждый поток читает все элементы одномерной текстуры и суммирует их. Соглашусь шейдер не практичен - результаты всех потоков идентичны. Для того что бы разные потоки читали свои массивы из разных участков двухмерной текстуры нужно подправлять код в ассемблере.
//описание семплера нужно как формальность, далее мы о нём совсем забудем.
SamplerState S
{
};
//текстура из которой будем читать данные
Texture1D Input;
/*описываем структуры входящих значений для шейдера (не константы и не данные текстуры )*/
struct VS_OUTPUT
{
float2 pos : POSITION;
/*семантика нужна компилятору, он думает что у нашего шейдера есть некая графическая специфика. Семантику POSITION*, где * - номер элемента текстуры можно прописать к любому типу поля - даже вашему собственному*/
};
struct PS_OUTPUT
{
int bla: SV_Target1;
/*ситуация аналогична предудущей*/
};
float get(Texture1D t, float coord)
{
return t.Sample(S,coord);
//для того что бы забыть о семплерах совсем
}
PS_OUTPUT main (VS_OUTPUT In)
{
PS_OUTPUT Output;
Output.bla = 0;//инициализация - иначе компилятор ругается
for(int i = 0; i < color="#cc0000"> return Output;
}
IMHO: HLSL может оказатся удобнее чем brook+, в нём неофициально есть классы и обьекты, по крайней мере документации я об этом не видел. Классы не поддерживают конструкторов/деструкторов, статических переменных/функций и виртуальных функций, но это не очень страшно по моему. Сама возможность инкапсуляции уже гуд. В 11-ом DirectX это всё появится официально плюс много других плюшек. Но думаю резон использовать ATI StreamComputing всё равно останетя - меньше задержки при обращении к устройству.
Пример валидного кода с классом :
class my
{
int4 a;
//это не конструкто
void my()
{
a = 2;
}
void bla();
};
void my::bla()
{
this.a += 1;
}
struct PS_OUTPUT
{
my bla: SV_Target1;
//подобное обьявление позволяет забыть о семантике и менять только свой класс
};
Недостатоков всего два :нужно править шейдер на асме (для доступа к номеру потока или local memory), и нужно переключатся между средами - что совсем не сложно. С другой стороны - AMD-шный компилятор HLSL содержит различны расширения - так что можно обращатся к ассемблеру редко. После выхода DirectX 11 первый недостаток отпадёт - можно будет писать на computing shaders.
Шаг 2. С помощью AMD GPUShaderAnalyzer 1.47 (или ниже))) - из более поздних такую возможность убрали ) получаем для нашего шейдера Intermediate Language.
Шаг 3. Написание хост кода использующего шейдер.
Код на C#, использующий вышеуказанный шейдер скомпилированный в CAL IL (унифицированный ассемблер видеокарт ATI, который если честно очень слабо отличается от D3D assembly).
string s = @"il_ps_2_0
dcl_cb cb0[1]
dcl_output_generic o0.x___
dcl_resource_id(0)_type(1d,unnorm)_fmtx(float)_fmty(float)_fmtz(float)_fmtw(float)
; l0 = (0.000000f 0.000000f 0.000000f 0.000000f)
; l0 = (0.000000f 0.000000f 0.000000f 0.000000f)
dcl_literal l0, 0x00000000, 0x00000000, 0x00000000, 0x00000000
mov r0.xyz_, l0
whileloop
ige r0.___w, r0.z, cb0[0].x
break_logicalnz r0.w
itof r0.___w, r0.z
sample_resource(0)_sampler(0) r1, r0.w
iadd r0.x___, r0.x, r1.x
; l2 = (0.000000f 0.000000f 0.000000f 0.000000f)
dcl_literal l2, 0x00000001, 0x00000001, 0x00000001, 0x00000001
iadd r0.__z_, r0.z, l2
endloop
mov o0.x___, r0.x
ret_dyn
end";
/*Инициализируем связь с CAL runtime, узнаём что у нас за устройство в системе и какими характеристиками оно обладает */
dotGPU.GPU g = new dotGPU.GPU();
/*компилируем нашу программу*/
g.CreateImage(s, 0);
//создаём текстурку - знаю было бы быстрее с использованием массивов.
var data = new List<int>();
data.Add(123);
data.Add(456);
//Копируем текстуру на видеокарту и получаем номер соответствующего ей ресурса
int n1 = (int)g.Allocate(0, false, data, 2, 1, 20);
var data1 = new List<int>();
data1.Add(2);
//аналогичный процесс для константы
int n2 = (int)g.Allocate(0, false, data1, 1, 1, 20);
int o = (int)g.Allocate(0, true, null, 64, 1, 20);
/*Запускаем на выполнение шейдер 0 на устройстве 0, размер домена выполнения(количество тредов) равен размеру аргумента 2, связываем хендлеры ресурсов с регистрами шейдера и получаем в результате хендлер задачи*/
int e = (int)g.Execute(0, 0, 2,
new List<string>() { "i0", "cb0", "o0" },
new List<int>() { n1,n2,o});
//ждём пока не закончится выполнение шейдера
while (!g.Wait(0, e));
//забираем результат
data1 = g.GetResult(o);
//завершаем работу с видеокартой - это нужно делать явно!
g.Close();
Шаг 4. Release)
Шаг 5. Debug))))))