четверг, 26 августа 2010 г.

Slow GLSL linking

Я только диву даюсь, какие сюрпризы можно отгребрать, юзая ГлюкоGL.

В портируемом рендерере кости для персонажей заливаются небольшими порциями: по 15-50 костей на секцию (лицо, руки, ноги и т. д.) Вот пришла в голову такая оптимизация: кости для всех видимых в кадре skinned персонажей заливать за один "присест" в bindable buffer, а в шейдере использовать offset для выборки. Т. к. костей (матрицы 3x4) много (> 5000), то по идее мы нехило разгружаем драйвер, делая такой батчинг. В D3D9 так поступить не смогли, ну а тут сам бог велел: у нас GLSL + SM 4.0 min. sys. req.

Т. к. float4 получается много (5000 * 3 = 15000 и более), а максимальный размер bindable buffer - 4K float4, то пришлось использовать 5 буферов, и хранить дополнительно индекс буфера, из которого шейдер должен сделать выборку. И вот тут то у меня и ждал "приятный сюрприз": время компоновки шейдеров при использовании магических строчек вида
bindable uniform vec4 bones[4096];
начало дико увеличиваться: для ~100 пар vs/ps с 10 с. до 10 минут! Да-да. Меняю размеры буферов и их количество до одного - время уменьшается. OMFG, что же ты там, зараза, можешь такое делать?! Какой индус решил повы*быв***ся своими знаниями в оптимизации кода при компоновке этих шейдеров? Эту компоновку, как известно, все нормальные разработчики видели в гробу, в белых тапочках... Короче кидаюсь на Mac, начинаю лихорадочно проверять работу кода на GF и ATI - всё нормально, ~100 пар линкуется за 5 с! Слава фюреру Стиви, в Apple следят за качеством OpenGL драйверов.

Ну хоть батчинг не придётся сливать в унитаз, а вот отладка в Windows превратилась в кошмар - > 10 мин на загрузку уровня.

"Ревизор" Гоголя

Воистину, каждый человек ближе к 30 должен перечитать/пересмотреть ещё раз это произведение. Потому что оно - гениально.

воскресенье, 22 августа 2010 г.

OpenCL

Случайно наткнулся на инфу о том, что ATI ввели interoperability между OpenCL и Direct3D 10. Не то чтобы меня сильно интересовали OpenCL/CUDA, но ATI отказывается делать поддержку DirectCompute 4.1 для low-level и мобильных 4-х тысячников (кроме HD 47xx и 48xx). А хочется, например, силуэты считать на GPU, а не на калеке x86.

Для взаимодействия с D3D 10 Khronos ввели расширение cl_khr_d3d10_sharing (кстати у NVidia уже доступно взаимодействие с D3D 11). ATI Stream SDK 2.2 его уже поддерживает, для моей карточки - в режиме беты. Я запускал сэмплы - работают, производительность так себе, ну да всё равно "на поиграться" хватит.

Поэтому решил потихоньку освоиться в OpenCL и заюзать его, где получится, вместо DirectCompute. Сегодня наваял тестовую программу, которая инициализирует этот самый "ОпенКЛ", загружает kernel для суммирования элементов двух массивов, запускает на выполнение и считывает результат в сис. память. Работает.

Плохо только, что нет поддержки D3D 11 (хотя, по сути, дело времени), и что надо ставить дополнительно драйверы OpenCL, чтобы заменить DirectCompute.

суббота, 21 августа 2010 г.

Gattaca

Think about this:
Anton
Vincent... Vincent...
Where's the shore? We’re too far out...
Vincent
You wanna quit?
Anton
We’re too far out!!!
Vincent
You wanna quit?!?!
Anton
No!

...

Anton
Vincent!.. How are you doing this, Vincent?
How have you done any of this?
We have to go back.
Vincent
It's too late for that, we're closer to the other side!

(Anton looks towards the empty horizon)

Anton
What other side?
You wanna drown us both?!
Vincent
You wanna know how I did it?
This is how I did it, Anton:
I never saved anything for the swim back!

понедельник, 16 августа 2010 г.

Галантный век

Всем техно-задротам посвящается.
Ну вы, короче, поняли :)

воскресенье, 8 августа 2010 г.

Ці руки нічого не крали, то дайте хоча б зараз щось вкрасти!

...

Чья упитанная морда на тебя глядит с биг-борда?
Чей тюремный лексикон до оскомины знаком?
И не комми, и не соци, и не националист -
неустанно в гору рвется вор, бандит, рецидивист.

среда, 4 августа 2010 г.

Slow OpenGL vertex pipe

На работе в проекте столкнулись со следующей проблемой: OpenGL медленно работает при большом количестве DIP-ов.

Дипов много: на тестовом уровне - около 4000. На каждый вызов приходится смена вершинного и индексного буферов, установка шейдерных юниформов, текстур и иногда кое-каких стейтов. Direct3D версия каким-то образом умудряется всё это расхлёбывать - видно что CPU bound, но performance в пределах допустимого. Наш же OpenGL порт в текущем виде догнать Direct3D версию, похоже, не в состоянии. С вопросами "почему так много" - не ко мне, мы всего лишь делаем порт существующего Direct3D рендера, который работает (у)довлетворительно.

Разобрал енджин так, чтобы рисовались только анимированые персонажи в wireframe - получилось в районе 400 дипов при 30-40 персонажах. Direct3D рендер переваривает это за 14-15 ms, OpenGL - вдвое медленнее. Применение различных constraints на кол-во установок стейтов показали, что тормозит связка glVertexAttribPointer()/glDrawElements(). Снизить кол-во VAP-ов (3000) невозможно, т. к. нет другого способа указывать драйверу, откуда брать вершинные данные. Существует расширение GL_ARB_vertex_array_object, но пока на моей конфигурации (WinXP + GeForceПЕЧ 260) оно не даёт ощутимого прироста.

Хуже всего, что в сети почти нет никакой информации, как можно соптимизировать такую ситуацию хотя бы под одного вендора (NVIDIA). Гугл выдаёт ссылки на треды и пейпры, в которых настоятельно рекомендуется "подключить VBO". Что делать, если уже использован оптимальный codepath, неясно.

Думаю отложить на время все свои изыски "в прекрасном и высоком" и заняться написанием демки, в которой будет рисоваться множество однотипной геометрии (сферы или ещё что-то), и посмотреть, как ведёт себя драйвер и сколько fps можно из него выжать при 1k дипов и разных форматах вершины.

Мне просто стало интересно.

Update:
Сидел на выходных, писал тест-апп. Рисуется моделька Макрона (финальный босс в Quake 2) 1000 раз, по дипу на модельку (я привёл только меш крупным планом, в демке они все крутятся на своих позициях в виде решётки). В меше (дипе) - 1200 треугольников. В вершине - позиция и нормаль, также делал выравнивание вершины до 32 байт. 

Удалось уложиться в 38 ms (~26 fps) на моём ноуте с HD 4330 и проце Celeron M. IMHO очень неплохо - 1 200 000 треугольников на мобильной конфигурации. Завтра побегу тестировать на рабочей лошадке. 

Update:

156 fps при 1000 дипов (Core 2 Quad + GTX 260). Не всё так плохо.

Выяснилась интересная подробность работы железа: если шейдер не использует какие-то вершинные атрибуты из буфера вершин, то они не читаются из видеопамяти. Т. е. скажем, формат вершины может быть хоть 100 байт, но если вершинный шейдер использует только позицию, то выбираться из памяти будут только соответствующие данные, а не вся вершина. При задействовании же всех атрибутов из 40 кб вершины в указанном выше тесте скорость падает до 50 fps.

Update:

Перенёс программу на Mac (это было просто, т. к. я использовал glut). При выборке в вершинном шейдере всех атрибутов на GeForce наблюдается падение скорости, аналогично как это происходит в Windows. На iMac с HD 4850 падения скорости - нет, fps держится в пределах 120 кадров, т. е. приблизительно аналогично GTX 260 в лёгком режиме. И ещё одно - на Mac+ATI gl_Position не мэпится на нулевой generic attribute, как и положено по стандарту.

понедельник, 2 августа 2010 г.

Silhouette compute shader

Написал тестовую реализацию на CPU и окончательно определился, как считать силуэты в вычислительном шейдере.

Вообще, силуэты можно искать двумя способами:
1) Идём по всем треугольникам, для каждого определяем, различается ли знак скалярного произведения (между нормалью и позицией) каждого смежного треугольника, и если да, то добавляем общее для этих треугольников ребро в силуэт.
2) Идём по всем рёбрам, для каждого определяем знак скалярного произведения смежных треугольников, если они различаются, добавляем ребро в силуэт.

Для первого способа адаптирован геометрический шейдер. Он несколько менее эффективный чем второй, т. к. на каждый треугольник приходится делать по четыре dot-a, хотя теоретически достаточно одного. Учитывая, что shading rate у GS низкий, это приемлемо.

Второй способ идеально подходит для вычислительного шейдера. Расчёт силуэтов проводим в два этапа: на первом определяем ориентацию всех треугольников относительно всех сэмплов, на втором - проверяем ориентацию смежных треугольников, добавляем ребро.

Первый шаг можно описать псевдокодом как-то так: step 1
Т. е. CS получает на вход буфер нормалей треугольников, делает для каждого сэмпла dot() и считает маску. Маска записывается в write-only буфер.

Для второго шага определяем структуры следующего вида:
struct edge
{
  uint a;
  uint b;
  uint left;
  uint right;
};

struct sil_edge
{
  vec3 a
  vec3 b;
  uint mask;
}
Где а, b - индексы в буфере вершин (во второй структуре - сами вершины), left, right - индексы смежных треугольников, mask - маска сэмплов, для которых ребро определяется как силуэтное.

На входе CS - буфер вершин меша и буфер структур edge, на выходе - буфер структур sil_edge. Каждый тред занят своим ребром, псевдокод второго шага: step 2. Силуэтные рёбра получаются уникальными, но каждое хранит маску сэмплов, для которых ребро определено как силуэтное. На этапе вытягивания волюма ещё один CS (или GS, но тогда придётся дублировать маску для каждой вершины, что suxx) может из одного ребра вытянуть несколько квадов, ориентируясь по маске.

Вот такой вот простой алгоритм, но на VS/GS его не реализовать, спасает только GPGPU.

воскресенье, 1 августа 2010 г.

Salt

Хорошая киношка! Намного лучше этой гадости, "Начала".
Джоли лапочка.