воскресенье, 4 октября 2009 г.

Shader Model 5.0 Dynamic Linking

Интересно стало, как же устроена данная фича. Во-первых: работает только в SM 5.0. Т. е. это не просто какая-то заумная идея замены #ifdef в шейдерах, а реализована посредством поддержки в железе. Во-вторых: ни в SDK, ни в MSDN нет описания ассемблера Shader Model 5.0. Четвёртого есть, пятого - нет, поэтому остаётся только догадываться, что означает та или иная запись/команда. Во всем Гугле есть только одна ссылка касательно ассемблера пятой модели - какой-то японец тоже активно копает эту тему. Думаю скоро Google проиндексирует и этот пост :)

Разобравшись с механизмом подачи инстансов реализаций в шейдер, я набросал простенький код и несколько вариаций на тему, чтобы понять, как всё работает. Исходный код:
interface IColor
{
float3 Get();
};

class Red : IColor
{
float x;
float3 Get() { return float3(1.0, 0.0, 0.0); }
};

class Green : IColor
{
float x;
float3 Get() { return float3(0.0, 1.0, 0.0); }
};

IColor g_Color;

cbuffer cbClassInstances
{
Red g_Red;
Green g_Green;
}

float4 VS(float4 vPos : POSITION) : SV_Position
{
return vPos;
}

float3 PS(float4 vPos : SV_Position) : SV_Target
{
return g_Color.Get();
}

Есть переменная абcтрактного типа и два инстанса реализаций. Данный вариант назовём условно 1x2. Ассемблерный выхлоп:
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_function_body fb0
dcl_function_body fb1
dcl_function_table ft0 = {fb0}
dcl_function_table ft1 = {fb1}
dcl_interface fp0[1][1] = {ft0, ft1}
dcl_output o0.xyz
dcl_temps 1
fcall fp0[0][0]
mov o0.xy, r0.xyxx
mov o0.z, l(0)
ret
label fb0
mov r0.xy, l(0,1.000000,0,0)
ret
label fb1
mov r0.xy, l(1.000000,0,0,0)
ret

Видно что объявляются прототипы функций (dcl_function_body). Далее мои фантазии. Ниже, объявляется таблица функций с "указателями" на адреса (метки). Каждая таблица содержит в фигурных скобках перечисление имён функций (меток), принадлежащих конкретной реализации интерфейса. Ещё строкой ниже - наш интерфейс, который связывается с одной из перечисленных в фигурных скобках таблицей функций. Какой именно - зависит от того, какой инстанс передать при вызове ID3D11DeviceContext::PSSetShader(). Функции ::Get() реализованы после основного кода шейдера как пары label/ret, легко проследить что кому принадлежит. Вызов нужного метода по установленному адресу (метке) производится новой командой fcall.

Вариант 1x3 отличается тем, что добавлена третья реализация IColor, Blue:
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_function_body fb0
dcl_function_body fb1
dcl_function_body fb2
dcl_function_table ft0 = {fb0}
dcl_function_table ft1 = {fb1}
dcl_function_table ft2 = {fb2}
dcl_interface fp0[1][1] = {ft0, ft1, ft2}
dcl_output o0.xyz
dcl_temps 1
fcall fp0[0][0]
mov o0.xyz, r0.xyzx
ret
label fb0
mov r0.xyz, l(0,0,1.000000,0)
ret
label fb1
mov r0.xyz, l(0,1.000000,0,0)
ret
label fb2
mov r0.xyz, l(1.000000,0,0,0)
ret

Часть кода из варианта 2x2:
IColor g_Color0;
IColor g_Color1;

float3 PS(float4 vPos : SV_Position) : SV_Target
{
return g_Color0.Get() + g_Color1.Get();
}

Ассемблер 2x2:
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_function_body fb0
dcl_function_body fb1
dcl_function_body fb2
dcl_function_body fb3
dcl_function_table ft0 = {fb0}
dcl_function_table ft1 = {fb1}
dcl_function_table ft2 = {fb2}
dcl_function_table ft3 = {fb3}
dcl_interface fp0[1][1] = {ft0, ft1}
dcl_interface fp1[1][1] = {ft2, ft3}
dcl_output o0.xyz
dcl_temps 2
fcall fp0[0][0]
fcall fp1[0][0]
mov r0.z, l(0)
mov r1.z, l(0)
add o0.xyz, r0.xyzx, r1.xyzx
ret
label fb0
mov r0.xy, l(0,1.000000,0,0)
ret
label fb1
mov r0.xy, l(1.000000,0,0,0)
ret
label fb2
mov r1.xy, l(0,1.000000,0,0)
ret
label fb3
mov r1.xy, l(1.000000,0,0,0)
ret

И ассемблер 2x3:
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_function_body fb0
dcl_function_body fb1
dcl_function_body fb2
dcl_function_body fb3
dcl_function_body fb4
dcl_function_body fb5
dcl_function_table ft0 = {fb0}
dcl_function_table ft1 = {fb1}
dcl_function_table ft2 = {fb2}
dcl_function_table ft3 = {fb3}
dcl_function_table ft4 = {fb4}
dcl_function_table ft5 = {fb5}
dcl_interface fp0[1][1] = {ft0, ft1, ft2}
dcl_interface fp1[1][1] = {ft3, ft4, ft5}
dcl_output o0.xyz
dcl_temps 2
fcall fp0[0][0]
fcall fp1[0][0]
add o0.xyz, r0.xyzx, r1.xyzx
ret
label fb0
mov r0.xyz, l(0,0,1.000000,0)
ret
label fb1
mov r0.xyz, l(0,1.000000,0,0)
ret
label fb2
mov r0.xyz, l(1.000000,0,0,0)
ret
label fb3
mov r1.xyz, l(0,0,1.000000,0)
ret
label fb4
mov r1.xyz, l(0,1.000000,0,0)
ret
label fb5
mov r1.xyz, l(1.000000,0,0,0)
ret

Как видим, код каждой реализаций дублируется столько раз, сколько у нас объявлено интерфейсов. Очевидно, что весь этот код не участвует в работе шейдера (а только тот, который вызывается), но он пожирает слоты инструкций и дольше загружается в кэш. По каким-то причинам два одинаковых интерфейса не могут вызывать функцию по одному и тому же "адресу", и они плодятся как кролики :) Возможно, так получается эффективнее в случае потоковой архитектуры GPU.

И последнее. Пускай наш интерфейс продиктует необходимость реализации ещё нескольких незамысловатых функций:
interface IColor
{
float3 Get();
float Alpha();
float Intensity();
};

Остальной код я не привожу, думаю от понятен. Ассемблерный выхлоп для 1x2:
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_function_body fb0
dcl_function_body fb1
dcl_function_body fb2
dcl_function_body fb3
dcl_function_body fb4
dcl_function_body fb5
dcl_function_table ft0 = {fb0, fb2, fb4}
dcl_function_table ft1 = {fb1, fb3, fb5}
dcl_interface fp0[1][3] = {ft0, ft1}
dcl_output o0.xyzw
dcl_temps 2
fcall fp0[0][0]
fcall fp0[0][1]
fcall fp0[0][2]
mov r0.z, l(0)
mul o0.xyzw, r0.xyzw, r1.xxxx
ret
...
...

Комментариев нет:

Отправить комментарий