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

Math terms

Если вам доводилось писать комплексные BRDF в шейдере, то наверняка вставала задача, как оптимально разбить вычисление компонентов формулы и как назвать переменные, которые бы хранили промежуточный результат расчётов.

Часто можно встретить названия переменных вроде amb, что означает "a minus b". Между тем, немногие знают, что существует достаточно богатая, исторически сложившаяся терминология, применяемая к компонентам математических выражений. Простейший пример, известный всем: числитель (numerator) и знаменатель (denominator). Но есть и термины, известные только узкому кругу математиков.

Вот я нашёл прекрасный толковый словарь математических терминов, составленный Pat Ballew:

MATH WORDS, AND SOME OTHER WORDS, OF INTEREST

А вот список терминов, которые я "накопал" - они могут (теоретически) применяться в обозначении того или иного term'а формулы:

Addend
Subtrahend
Minuend
Residue
Multiplicand
Multiplier
Product
Mean
Magnitude
Divident (Numerator)
Divisor (Denominator)

В словаре толкуется историческое происхождение этих и многих других математических терминов, в основном это корни древних латыни и греческого языков. Думаю, многим будет интересно узнать происхождение слова "compute" :)

среда, 7 октября 2009 г.

Research In Refraction

Ранее я уже писал, что хочу заняться преломлениями и отражениями. Поэтому я начал собственный research в этой области, т. к. задача нетривиальная. В перспективе это должно вылиться в кульное DX11-demo.

Сначала разберёмся с физической стороной проблемы. Преломление - это изменение направления следования светового луча, возникающее на границе двух прозрачных сред с различной плотностью. В вакууме скорость света c составляет приблизительно 299 800 км в секунду. Когда луч путешествует в среде с ненулевой плотностью, его скорость замедляется. Замедление является функцией от длины волны и материала, из которого состоит среда. Чем выше плотность, тем сильнее замедляется луч. Это не проходит бесследно, видимый эффект заключается в изменение угла между нормалью преломляющей поверхности и направлением движения луча. Попав опять в вакуум, луч света приобретает прежнюю скорость с, а угол восстанавливается.

Преломление света описывается законом Снелла.

Для того, чтобы рассчитать, как луч будет преломляться на границе двух сред, используются индексы рефракции материалов, из которых эти среды состоят. Индекс рефракции n определяется по формуле:
n = c/v
где с - скорость распространения света в вакууме, v - скорость распространения в материале.

Индекс рефракции в вакууме равен 1, а это значит, что индекс рефракции любого материала всегда больше 1. Чем выше плотность, тем сильнее замедляется луч, тем выше становится индекс рефракции. Наименьшие индексы имеют различные газы, наибольшие - кристаллы. Вот пожалуй и всё по теории.

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

Видно, что направление движения луча восстанавливается после того, как он покидает стекло. К сожалению, если использовать стандартный приём - выборку из кубической карты по вектору, никакого эффекта преломления не увидеть. Нужен честный рэйтрейс.

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

Кварц:

Рубин:

Алмаз (диамант):

Проверка математики, если в линзе воздух:

А вот так свет будет преломляться, если в линзе будет воздух и она (линза) будет помещена в воду:

воскресенье, 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
...
...

пятница, 2 октября 2009 г.

GT300, игры и борьба с пиратством

NVIDIA пошла на досрочное разглашение подробностей нового поколения своих GPU... Видимо пытаются подогреть интерес из-за проблем с выпуском годных чипов. Если всё, что они понаписывали - правда, и новый чип может исполнять хоть в каком-то виде код C++ - то Intel пора хвататься за голову. Можно представить себе, как в 2012 выйдёт новый универсальный потоковый процессор от NVIDIA на её же материнской плате, выполняющий через эмулятор код старых x86 программ в 10 раз быстрее какого-нибудь 8-ядерного Intel Core i10... Помните, что случилось с Silicon Graphics?

PS. А пока что NVIDIA показывает фейковые прототипы с отпиленной (ножовкой) PCB. Интересно, был ли хоть чип под системой охлаждения? Совсем обнаглели.

Ещё одна интересная тема - а зачем, собственно, делать новые видеокарты, если рынок PC-игр, похоже, верно загибается? Всякая шняга, конечно, на нём всегда будет присутствовать, но на новые крупные многомиллионные тайтлы (способные показать "красу" и утилизировать мощь топовых видеокарт) придётся закатать губу. Crytek фичекатят енджин под дряхлый PS3 - "больше никаких эксклюзивов на PC". Есть правда Rage... Думаю, что чем больше распиарят новые DirectX 11 игры, тем быстрее их повзламывают и повыкладывают на торрентах. Замкнутный круг. Пока отсутствие софта не скажется на спросах на железо, которое этот самый софт должно запускать, HW-вендоры, похоже, проблему нелицензионного копирования решать не намерены.

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

Шейдеры в виде исходных кодов хранятся только на сервере издателя. В процессе первого запуска игры юзер подключается к серверу издателя игры (нужен интернет), вводит серийный номер с диска и задаёт свои уникальные имя/пароль, игра отсылает их на сервер. Если сервер подтверждает уникальность серийного номера копии игры, то дальше игра отсылает модель GPU, уникальный GPUID, версию установленного видеодрайвера и т. д. Затем на сервере хранящиеся там шейдеры компилируется в бинарные блобы, подходящие под GPUID и видеодрайвер пользователя и шифруются ключом (на сервере хранится таблица пар GPUID/ключ). Получается такой shader cache из зашифрованных бинарных шейдеров, который единожды передаётся и инсталлируется на машине игрока. В таком виде шейдеры загружаются в видеокарту, а там GPU использует свой ключ для их дешифровки перед загрузкой кода в видеопамять. Предполагается, что прочитать из видеопамяти расшифрованный бинарник невозможно.

Всё. Полученный shader cache будет работать только с тем GPU, ключ которого соответствует ключу, использованного при его шифровании. Если пользователь сменил аппаратную конфигурацию, то процедуру получения нового кэша придётся повторить, при этом введя свои имя/пароль и серийный номер диска. При этом "аккаунт" для старой железки становится невалидным.

четверг, 1 октября 2009 г.

Луна 2112

Посмотрел камрип. Зачётное кино! Люблю такую фантастику.
PS. Если что - камрип это потому, что у нас его не показывают в кинотеатрах. А так бы сходил.

вторник, 29 сентября 2009 г.

"Программистcкий" английский

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

Например, CPU и GPU. Многие произносят их как "Цэ-Пэ-У" и "Гэ-Пэ-У". Я так не могу! Путём некоторых усилий у меня выработалась привычка произносить эти аббревиатуры правильно: "Си-Пи-Ю" и "Джи-Пи-Ю".

Или вот другой пример. Популярное словечко "deferred" я раньше произносил с ударением на второй слог. Мой лид - с ударением на первый. При этом оба произношения можно было записать как "Дэферред". Я решил заглянуть в Google Dictionary и обнаружил, что звучит это слово на самом деле примерно как "Ди'фё(у)р(д)" (ударение на второй слог, "d" практически не слышно):
deferred. Круто.

Сделал вывод: надо не лениться и почаще заглядывать в Google Dictionary, а то стыдно сыпать доморощенными произношениями.

четверг, 24 сентября 2009 г.

ID3D11CommandList

Занятно, что в D3D 11 появился этот интерфейс (в 10 его не было, в 9 были какие-то функции для записи/воспроизведения стейтов), в то время как в OpenGL 3.2 дисплейные списки убрали. Слышал, что парни из NVIDIA недовольны таким решением - ещё бы, после стольких лет отладки этих самых списков! Тред на эту тему можно почитать здесь: Siggraph Asia 2008 slides suggestions for OpenGL.

Вся беда в том, что хоть у NVIDIA они и отлажены, но в целом это дикая штуковина. Вот пример:
glNewList(42, GL_COMPILE);
glVertex3f(0,1,0);
glColor3f(1,0,0);
glVertex3f(1,1,1);
glEnd();
glEndList();

glBegin();
glColor3f(0,1,0);
glVertex3f(0,0,0);
glColor3f(0,0,1);
glCallList(42);
// note the lack of glEnd() - it's in the display list!

В общем, комитет отправил эту хрень в преисподнюю, вслед за immediate mode.

В Direct3D 11 схема работы следующая: можно создать один immediate контекст, для которого вызовы складываются в command buffer и выполняются, а можно в дополнение создать множество deferred контекстов, для которых вызовы складываются во внутренний command list, и после вызова ID3D11DeviceContext::FinishCommandList() вы получаете указатель на объект интерфейса ID3D11CommandList, в котором записаны все команды, поданные со времени предыдущего вызова ::FinishCommandList() или со времени создания контекста. Затем этот command list можно выполнить на immediate контексте вызовом ID3D11DeviceContext::ExecuteCommandList().

Сначала я недоумевал, почему есть Finish функция, но нет Begin. Ответ прост - сами по себе любые вызовы для deferred контекста не имеют никакого значения, они не идут в command buffer, а значит, и обрамляющая пара Begin/End - не нужна.

Судя по тому, что написано в SDK, command lists формируются очень быстро (не так медленно, как скажем, стейты через ::Create*State()), их можно формировать каждый кадр, параллельно, на нескольких ядрах в многоядерной системе. Если же драйвер/железо не поддерживают это, то command lists всё равно полезны как средство формирования command buffer, скажем, перед циклом рендеринга. Правда, пока неясно, что именно туда можно складывать - помните, сколько было ограничений в OpenGL с этим: "Certain commands, when called while compiling a display list, are not compiled into the display list but are executed immediately."