Навигация
· XNA FAQ
· С чего начать
· Конкурсы
· Обратная связь
· XNA Блоги
Сейчас на сайте
· Гостей: 1

· Пользователей: 0

· Всего пользователей: 3,684
· Новый пользователь: headron
Последние фото
Эх, чуть не проспал закрытие.
Эх, чуть не проспал ...
Альбом: XNA Engine

GB
GB
Альбом: XNA Engine

South Park Coon & Friends
South Park Coon & Fr...
Альбом: XNA Games

Блоги
yavshoke
» XboxOne - интерес...
dampirik
» Push уведомления ...
dampirik
» Реклама,статистик...
Chort
» XNA и StartCoroutine
Chort
» Curve Class
dampirik
» Реклама, статисти...
dampirik
» Увеличение скорос...
dampirik
» Реклама, статисти...
general
» Распаковка DxtCom...
general
» Как работать с XN...
Поддержка
microsoft.com
1gb.ru - Дом для вашего сайта
Статистика посещений:

Меташейдеры
Теория

Что такое метапрограммирование?

Если программирование подразумевает собой реализацию алгоритмов обработки данных на каком-либо языке, то метапрограммирование это реализация алгоритмов для работы с самими программами, представленными в виде исходного кода или еще как то. Проще говоря, метапрограммирование – это создание программ, создающих другие программы или манипулирующих ими и даже собой.

C# является ООП языком и предоставляет возможности практически на прямую использовать формальное описание объектной модели предметной области решаемой задачи. Среди основных понятий и принципов ООП отметим следующие – Абстракция данных, Инкапсуляция, Наследование, Полиморфизм.

Полиморфизм рассмотрим немного подробнее, так как именно его преимущества лежат в основе предлагаемого подхода. Полиморфным будет тот код, который при вызове какой либо функции с определенным (одним и тем же) именем использует различные типы передаваемых аргументов, что обеспечивает выполнение разного конечного кода. На практике это значит, что у нас есть возможность выполнять контекстно-зависимый код, где контекст определяется типом аргумента вызываемой функции. Собственно это и есть то преимущество полиморфизма, которое позволит нам создавать HLSL шейдеры на языке C#.

Полиморфизм глазами C#

Тут за примером ходить далеко не придется, полиморфизм используется практически везде. Возьмите рефлектор и откройте декларацию класса System.IO.BinaryWriter. У метода Write(..) есть 17 вариантов реализации. Кроме этого – вот еще небольшой пример для наглядности –

Предположим у нас есть структура для описания вершин –

1
2
3
4
5
6
struct VertexPositionNormalTexture
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
}

 

Теперь нам необходимо сохранить ее в бинарном виде. Для этого добавим еще «немного полиморфизма» в стандартный BinaryWriter. Для добавления используем наследование.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBinaryWriter : BinaryWriter
{
public void Write(Vector2 v2)
{
Write(v2.X);
Write(v2.Y);
}
public void Write(Vector3 v3)
{
Write(v3.X);
Write(v3.Y);
Write(v3.Z);
}
}

 

MyBinaryWriter уже содержит 19 вариантов реализации метода Write(..).  Наша структура, теперь умеющая сохранять себя, будет выглядеть так –

1
2
3
4
5
6
7
8
9
10
11
12
13
struct VertexPositionNormalTexture
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
 
public void Write(MyBinaryWriter writer)
{
writer.Write(Position);
writer.Write(Normal);
writer.Write(TextureCoordinate);
}
}

 

Получилось наглядно и работать с этим удобно будет...

Одной из разновидностей полиморфизма в C# является перегрузка операторов. XNA использует это для обеспечения наглядности при работе с векторными и матричными типами данных.

Сравните два метода, выполняющих одни и те же вычисления -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Matrix GetTransform1(ref Matrix world, ref Matrix view, ref Matrix proj)
{
Matrix result;
 
Matrix.Multiply(ref world, ref view, out result);
Matrix.Multiply(ref result, ref proj, out result);
 
return result;
}
 
Matrix GetTransform2(Matrix world, Matrix view, Matrix proj)
{
return world * view * proj;
}

 

Эти методы равнозначны. Только первый из них реализован с учетом производительности (порядка 20% будет выигрыш). А второй с учетом наглядности и использует перегруженный оператор умножения.

См. рефлектор, struct Matrix - public static Matrix operator *(Matrix matrix1, Matrix matrix2);

Меташейдеры для XNA

Основная идея очень проста – что если в перегруженных операторах выполнять не сами вычисления, а попытаться сохранить их логику? Очевидно, что для формального описания логики вычислений нам понадобится ее объектная модель. Тут нужна модель, описывающая как ход этих вычислений, так и любой другой ход программы, возможный в рамках представляемого языка, в нашем случае HLSL. Благо рассматриваемый нами язык не так богат на возможности и классов из System.CodeDom нам вполне хватит.

Рассмотрим простейший пиксельный шейдер, складывающий диффузную и спекулярную компоненты освещения –

1
2
3
4
float4 PSBasic(in float4 Diffuse: COLOR0, in float4 Specular : COLOR1) : COLOR
{
return Diffuse + Specular;
}

 


Для написания метапрограммы, результатом которой будет аналогичный код - определим новый тип данных float4 и перегрузим оператор сложения для него.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct float4
{
public CodeExpression Expression;
 
public float4(CodeExpression expression)
{
this.Expression = expression;
}
 
public static float4 operator +(float4 left, float4 right)
{
CodeBinaryOperatorExpression expression;
 
expression = new CodeBinaryOperatorExpression(
left.Expression, CodeBinaryOperatorType.Add, right.Expression);
 
return new float4(expression);
}
 
public static implicit operator float4(string argName)
{
return new float4(new CodeArgumentReferenceExpression(argName));
}
 
public override string ToString()
{
CSharpCodeProvider cSharp = new CSharpCodeProvider();
 
StringBuilder hlslCode = new StringBuilder();
StringWriter writer = new StringWriter(hlslCode);
CodeGeneratorOptions options = new CodeGeneratorOptions();
 
cSharp.GenerateCodeFromExpression(Expression, writer, options);
 
return hlslCode.ToString();
}
}

 

Обратите внимание на реализацию метода ToString(). Мы используем CSharpCodeProvider, для теста этого достаточно но для более сложных вариантов понадобится свой HlslCodeProvider наследованный от базового.

Теперь мы можем во время исполнения видеть логику хода программы.


И главное можно не только наблюдать ее но и анализировать. Для более удобного анализа есть смысл построить свой CodeDom для Hlsl. Из которого убрать все лишнее. Добавить реализацию паттерна посетитель и пр.. Но это уже детали реализации, а цель этой статьи - раскрыть торию меташейдеров.

К слову о реализации - есть такой коммерческий 3D движек Visual3D, в нем используется похожий принцип. Система материалов построена на меташейдерах. В качестве бонуса к этой статье, если хватит времени до окончания конкурса, будет предложена более грамотная реализация рассматриваемого подхода. Правильный подход поразумевает использование объектой модели конечного языка (CodeDom), как это было показано в данной статье. В Visual3D в перегруженных операторах используют обычную конкатенацию строк. В результате имеем сразу исходник Hlsl, что ограничивает возможности анализа, манипулиции и оптимизации конечного кода.

На данный момент есть полурабочий прототип, позволяющий декларировать шейдеры на шарпе используя синтаксис схожий с HLSL -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
struct VSInput
{
[POSITION]
public float4 Position;// : POSITION;
}
public struct CommonVSOutput
{
public float4 Pos_ws;
public float4 Pos_ps;
public float4 Diffuse;
public float3 Specular;
public float1 FogFactor;
}
public struct VertexLightingVSOutput
{
[POSITION] // Position in projection space
public float4 PositionPS;// : POSITION;
 
[COLOR0]
public float4 Diffuse;// : COLOR0;
 
[COLOR1]// Specular.rgb and fog factor
public float4 Specular;// : COLOR1;
}
 
public struct VertexLightingPSInput
{
[COLOR0]
public float4 Diffuse;// : COLOR0;
[COLOR1]
public float4 Specular;// : COLOR1;
}
public struct VertexLightingPSInputTx
{
[COLOR0]
public float4 Diffuse;
[COLOR1]
public float4 Specular;
[TEXCOORD0]
public float2 TexCoord;
}
 
public float1 FogStart;
public float1 FogEnd;
public float1 FogEnabled;
public float3 FogColor; //: register(c3);
 
public texture BasicTexture;
 
private sampler TextureSampler = sampler_state(
"BasicTexture",
MipFilter.Linear,
MinFilter.Linear,
MagFilter.Linear);
 
public float4x4 World; //: register(vs, c20); // 20 - 23
public float4x4 View; //: register(vs, c24); // 24 - 27
public float4x4 Projection; //: register(vs, c28); // 28 - 31
public float3 EyePosition; //: register(c4); // in world space
public float3 DiffuseColor = 1; //: register(c5) = 1;
public float1 Alpha = 1; //: register(c6) = 1;
public float3 EmissiveColor = 0; //: register(c7) = 0;
public float3 SpecularColor = 1; //: register(c8) = 1;
public float1 SpecularPower = 16; //: register(c9) = 16;
 
 
public float1 ComputeFogFactor(float1 d)
{
return clamp((d - FogStart) / (FogEnd - FogStart), 0, 1) * FogEnabled;
}
 
public CommonVSOutput ComputeCommonVSOutput(float4 position)
{
CommonVSOutput vout;
 
float4 pos_ws = mul(position, World);
float4 pos_vs = mul(pos_ws, View);
float4 pos_ps = mul(pos_vs, Projection);
vout.Pos_ws = pos_ws;
vout.Pos_ps = pos_ps;
 
vout.Diffuse = float4(DiffuseColor.rgb + EmissiveColor, Alpha);
vout.Specular = 0;
vout.FogFactor = ComputeFogFactor(length(EyePosition - pos_ws));
 
return vout;
}
 
VertexLightingVSOutput VSBasic(VSInput vin)
{
VertexLightingVSOutput vout;
 
CommonVSOutput cout = ComputeCommonVSOutput(vin.Position);
 
vout.PositionPS = cout.Pos_ps;
vout.Diffuse = cout.Diffuse;
vout.Specular = float4(cout.Specular, cout.FogFactor);
 
return vout;
}
 
[return: COLOR]
public float4 PSBasic(VertexLightingPSInput pin)// : COLOR
{
float4 color = pin.Diffuse + float4(pin.Specular.rgb, 0);
color.rgb = lerp(color.rgb, FogColor, pin.Specular.w);
 
return color;
}
 
[return:COLOR]
public float4 PSBasicTx(VertexLightingPSInputTx pin)// : COLOR
{
float4 color = tex2D(TextureSampler, pin.TexCoord) *
pin.Diffuse + float4(pin.Specular.rgb, 0);
 
color.rgb = lerp(color.rgb, FogColor, pin.Specular.w);
return color;
}

 

Это фрагмет шейдера BasicEffect из XNA framework. Логика выполнения функций отражена в перегруженных операторах и встроенных методах, таких как tex2D(), lerp(), mul(). Эти встроенные методы (Intrinsic Functions) определены в базовом классе шейдера и возвращают результат в виде MethodInvokeExpression. Все остальное (параметры, самплеры, техники) получаем используя рефлексию.

Тесты

Текущий прототип не может пока генерировать весь код HLSL эффекта из C# класса деларирующего шейдер. Но получить код отдельно взятых функций можно. Сравните то что получилось из вершинного шейдера объявленного выше -

HLSL код сгенерированный из С# (см. выше - строки 67-100)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
VertexLightingVSOutput VSBasic(VSInput vin)
{
VertexLightingVSOutput vout;
 
vout.PositionPS = mul(mul(mul(vin.Position, World), View), Projection);
 
vout.Diffuse = float4((DiffuseColor.rgb + EmissiveColor), Alpha);
 
vout.Specular = float4(0,0,0, clamp(((length((EyePosition - mul(vin.Position, World)))
- FogStart) / (FogEnd - FogStart)), 0, 1) * FogEnabled);
 
return vout;
}
 

 


Вершинный шейдер как будто стал короче )). Хотя на самом деле - появилось лишнее умножение - mul(vin.Position, World). На дебаг режим компиляции HLSL это повлияет, но в релизе оптимизатор это заметит и уберет.
Полученный HLSL код визуально смотриться короче просто потому, что тут развернуты все вызовы локальных функций - ComputeCommonVSOutput(..) и ComputeFogFactor(..). Разворачивание локальных функций происходит автоматом, это даже не фича, специально реализованная, это следствие самого подхода к кодогенерации.

Теперь вставим полученный код в fx файл, добавим техник и параметров по аналогии с оригиналом, скомпилируем и сравним ASM с дизассемблером того же эффекта но собранного полностью из оригинального HLSL.

ASM оригинального HLSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    vs_1_1
def c3, 0, 1, 0, 0
dcl_position v0
dp4 r1.w, v0, c23
dp4 r1.x, v0, c20
dp4 r1.y, v0, c21
dp4 r1.z, v0, c22
dp4 r0.x, r1, c24
dp4 r0.y, r1, c25
dp4 r0.z, r1, c26
add r2.xyz, -r1, c4
dp4 r0.w, r1, c27
dp3 r1.x, r2, r2
dp4 oPos.x, r0, c28
rsq r1.w, r1.x
rcp r1.w, r1.w
mov r2.w, c1.x
add r2.w, -r2.w, c2.x
add r1.w, r1.w, -c1.x
rcp r2.w, r2.w
dp4 oPos.y, r0, c29
mul r1.w, r1.w, r2.w
dp4 oPos.z, r0, c30
max r1.w, r1.w, c3.x
dp4 oPos.w, r0, c31
min r0.w, r1.w, c3.y
mov r0.xyz, c5
add oD0.xyz, r0, c7
mul oD1.w, r0.w, c0.x
mov oD0.w, c6.x
mov oD1.xyz, c3.x
 
// approximately 28 instruction slots

 

 



ASM сгенерированного HLSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    preshader
add c13.xyz, c2.xyz, c3.xyz
neg r1.w, c0.x
add r0.w, r1.w, c1.x
rcp c12.x, r0.w
 
// approximately 4 instructions
 
vs_1_1
def c18, 0, 1, 0, 0
dcl_position v0
dp4 r1.w, v0, c3
dp4 r1.x, v0, c0
dp4 r1.y, v0, c1
dp4 r1.z, v0, c2
dp4 r0.x, r1, c4
add r2.xyz, -r1, c17
dp4 r0.y, r1, c5
dp3 r2.x, r2, r2
dp4 r0.z, r1, c6
rsq r2.w, r2.x
dp4 r0.w, r1, c7
rcp r1.w, r2.w
dp4 oPos.x, r0, c8
add r1.w, r1.w, -c15.x
dp4 oPos.y, r0, c9
mul r1.w, r1.w, c12.x
dp4 oPos.z, r0, c10
max r1.w, r1.w, c18.x
dp4 oPos.w, r0, c11
min r0.w, r1.w, c18.y
mul oD1.w, r0.w, c14.x
mov oD0.xyz, c13
mov oD0.w, c16.x
mov oD1.xyz, c18.x
 
// approximately 24 instruction slots

 



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

Результат для меня лично неожиданный. На уровне ощущений понимаю почему так получилось, но формально выразить не могу пока, не хватает знаний терминологии из области создания компиляторов.

Плюсы и минусы

На XBox работать не будет - нет возможности компилировать HLSL код в рантайме.
При компиляции в Debug режиме можно получить более "тяжелый" HLSL/ASM код.
Если клонировать такие шейдеры без рефлексии - много ручной работы. (свой Clone() нужен везде)

Один из основных плюсов это объектно ориентированный подход в создании шейдеров.

Shawn Hargreaves (один разработчиков XNA) в своем блоге пишет -

1
2
3
4
5
"So, I have this shader that does normalmapping, 
and this other shader that does skinned animation.
How can I use them both to render an animated normalmapped character?"
 
Welcome, my friend, to one of the fundamental unsolved problems of graphics programming...

 

Комбинирование шейдеров является одной из основных нерешенных проблем программирования графики. Автор аргументирует это большой гибкостью программируемго конвеера, котрый и так и не просто иногда настроить, а не то что производить какие то манипуляции в полуавтоматическом режиме.

Что ж, я считаю что предложенный в статье подход имеет потенциал решить эту проблему. Если работа над меташейдерами будет продолжена и поддержана нами всеми - в итоге мы сможем автоматом получить normalmapping + skinned шейдер имея только его составляющие.

А еще можно без ручного кодирования биндить параметры меташейдера к EffectParameter через рефлексию. ))

На этом все..
Комментарии
#1 | general 23.10.2009 00:21:17
интересно. я все как то по старинке на этой теме сидел, теперь задумаюсь.
#2 | vanka 23.10.2009 00:40:56
На мой взгляд, то, что в статье называется полиморфизмом, на самом деле называется перегрузкой.
#3 | mike 23.10.2009 01:01:27
Если не ошибаюсь - перезагрузка не значится как один из основных принципов ООП.
В википедии очень похоже заблуждаются. Первое предложение определения полиморфизма верно. Но потом идет уклон в наследование...
#4 | general 23.10.2009 01:11:56
перегрузка это часть идеи полиморфизма. иерархия такая.
#5 | alexG 23.10.2009 16:38:25
Ну вот, с почином! Рад что на конкурсе не один теперь Smile Посоветовался с совестью и решил, что честно рад ))
Собственно идея очень понравилась. C#->HLSL->ASM - круто! Smile
Интересно будет увидеть сравнение в производительности - таки графика это пока такая область где в большинстве случаев вольность ограничивается требованиями быстродействия. Как будет реализована идея наследования и полиморфизма (именно полимрфизма как реализации одного и того же метода в разных классах иерархии наследования, использование base и virtual с abstract - дабы говорить так чтобы трактовали мой вопрос однозначно), inline подстановка или что то еще? Ну и пожалуй самое вкусное чего жду - рабочий прототип.
А вообще, на мой взгляд, это потенциальное поле деятельности для сообщества - вполне реалистично развить тему. Задумайтесь сеньоры Wink

P.S.: Good -> даешь продолжение!
#6 | general 23.10.2009 16:59:09
alexG полностью согласен
#7 | vanka 23.10.2009 17:13:39
Прошу тогда дать мне ссылку на "правильное" определение полиморфизма. Описанный в данной статье полиморфизм (на самом деле перегрузка) есть и в с++ без единого класса.
#8 | mike 23.10.2009 17:55:58
Обсуждение парадигм ООП предлагаю перенести в форум.
#9 | general 23.10.2009 19:42:37
горячая статья получилась Smile
#10 | alexG 03.11.2009 01:24:51
Однако удивлен - майкрософт то этим видать озаботилась довольно давно... В 11 DirectX есть Dynamic Shader Linkage, если вкратце то в 5ю модель HLSL добавили кое-какие элементы ООП, в примере точно видел интерфейсы, классы, не уверен насчет наследования классов и полиморфизма. Сейчас рыскаю.
Единственное - это не отменяет того, что вряд ли когда XNA под 10ый то DirectX будет доступна, так что идея все равно достойна реализации.
Добавить комментарий
Пожалуйста, залогиньтесь для добавления комментария.
Рейтинги
Рейтинг доступен только для пользователей.

Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.

Отлично! Отлично! 25% [1 Голос]
Очень хорошо Очень хорошо 50% [2 Голоса]
Хорошо Хорошо 25% [1 Голос]
Удовлетворительно Удовлетворительно 0% [Нет голосов]
Плохо Плохо 0% [Нет голосов]
Авторизация
Логин

Пароль



Вы не зарегистрированы?
Нажмите здесь для регистрации.

Забыли пароль?
Запросите новый здесь.
Мини-чат
Вы должны авторизироваться, чтобы добавить сообщение.

27.08.2014
Я умею немного на asp.net + html и css

22.08.2014
на ASP mvc 3 есть пару проектов. Могу помочь, если нужно. Обидно, если закроется Frown

21.08.2014
я тоже ноль

21.08.2014
Я в вебе только с php занимался да и то на уровне чтоб работало.

21.08.2014
Я в вебе полный ноль…

21.08.2014
Переводить его надо, хоть на ту же азуру. И двиг менять на что-то современное. Если есть веб-разрабы - можем скооперироваться. Один делать не буду.

21.08.2014
не знаю всех нюансов по оплате и все хорошее когда нибудь заканчивается

21.08.2014
А что случилось?

21.08.2014
похоже сайт будет работать до 28го числа

09.08.2014
Апи пока не видел. Но есть приложение в магазине Live Lock Screen BETA, так что думаю скоро будет

08.08.2014
Я про API для Update1. На нем работает это

08.08.2014
А что именно нужно? Чтото и сейчас открыто http://msdn.micro.
...105).aspx

06.08.2014
Кто-нибудь слышал об открытии доступа к Lock Screen Api?

31.07.2014
VPDExpress на базе MVS 2012, ни в какую не ловит исключения. Даже если их сам создаешь. И всех так?

25.07.2014
С днем системного администратора причастных к этой профессии! По случаю - тортик от жены

RSS каналы сайта
XNA - Новости
XNA - Статьи
XNA - Форум
XNA - Галерея
XNA - Файлы
Время загрузки: 0,08 секунд 8,709,579 уникальных посетителей