четверг, 31 декабря 2009 г.

2010

Я оптимизировал, как мог, исходный код сэмпла OIT11 из DX SDK и заставил его выдавать соответствующую D3D11 железу скорость.



Пока адресация заточена под базовые 640x480, поэтому не привожу fps для высоких разрешений. К тому же оптимизации не закончены. О плохом: к концу буфера кадра (внизу окна) вылязят ужасные ошибки. Не знаю, где точно проблема - то ли в read/write contention, то ли в адресации/распределении тредов. Будем бороться - у Мехи же получилось :)

На этой мажорной ноте и закончу research 2009 года. Дай бог, чтобы в следующем году у меня было хоть немного времени на все эти занятные штуковины.

Да!
C Новым Годом! С новым, так сказать, счастьем :)

понедельник, 28 декабря 2009 г.

Xerxes


Совсем недавно стало ясно, что продолжение "300 спартанцев" скорее будет, чем нет. Зак Снайдер подтвердил, что работает над приквелом с кодовым именем "Ксеркс".

Wow!

воскресенье, 27 декабря 2009 г.

Parallel Prefix Sum (Scan)

Несколько дней назад я тестировал сэмпл OIT11 из DirectX SDK и был неприятно поражён его производительностью: 12 fps в окне 320х240. Решив разобраться, в чём дело, я реализовал небольшую программу, приблизительно воспроизводящую алгоритм prefix sum из OIT11, а также проштудировал материал по теме.

Вот код программы: OIT prefix sum.
Во-первых, алгоритм сканирования реализован "в лоб". В процессе выполнения видно, что к концу сканирования он плохо параллелится - последний (единственный) тред суммирует половину фрагментов буфера кадра, что никак нельзя назвать параллельной работой. Во-вторых, асимптотическая оценка кол-ва операций этого алгоритма = O(N * log(N)), тогда как достаточно O(N) операций (более точно, этот алгоритм выполняет N * log(sqrt(N)) операций), т. е. для сколь-нибудь большого буфера кадра его производительность ниже оптимального на несколько (!) порядков.

Фактически, применённый алгоритм - это naive prefix scan, описание которого можно найти у NVIDIA. Для пробы я написал код и для него: Naive prefix sum. Асимптотическая оценка времени его выполнения тоже O(N * log(N)). Единственное отличие - для OIT11 не требуется промежуточный буфер, куда записываются результаты суммирования, а потом копируются обратно в основной буфер.

Наиболее оптимальный на сегодняшний день алгоритм был описан Guy E. Blelloch в 1990. Он состоит из двух фаз - reduce (up-sweep) и down-sweep. Алгоритм хорошо распараллеливается, т. к. каждый тред выполняет всего одно сложение (или сложение/обмен в down-sweep). Алгоритм выполняет ~(N * 2) операций, т. е. его асимптотическая оценка - O(N). Inclusive prefix sum.

P.S. Я не очень хорошо разбираюсь в CUDA и нюансах работы тредов, поэтому меня несколько смущают описанные проблемы конфликтов банков памяти при доступе из различных тредов. Я вот задумался: атомические операции в HLSL 11 призваны убрать именно эту проблему или они нужны для чего-то другого?

четверг, 24 декабря 2009 г.

Radeon HD 5770

Решил всё-таки купить новый Radeon. Подарок самому себе на Новый год, так сказать. Денег на 5800-ю серию нет (да и БП не потянет такого монстра), решил приобрести middle-end, а именно Sapphire Radeon HD 5770 1GB. Решил взять чуть подороже, зато Sapphire, чтобы быть уверенным, что производитель не втюхает мне какую-нить убогую память или ещё как-нить облапошит, подсунув законченный и совершенный low-end.  Карта мне обошлась в 202$, хотя рекомендованная цена от AMD - 160$. Барыги накручивают - в нищей стране, как известно, платят дважды.

Вот так выглядит коробка:



Внутри видеокарта в антистатическом пакете (с громким предупреждением о необходимости в дополнительном питании для платы!), переходник Molex/6-PinPCI-E, инструкция для упоротых, диск с драйверами под XP/Vista/Seven, переходник DVI/VGA и перемычка для CrossFire:



Вот сама видеокарта в исполнении Sapphire:



А это она уже в системном блоке, с подключенным питанием:



Пыльновато тут, завтра надо бы почистить трудягу. Кстати о питании. AMD рекомендует для HD 5770 и HD 5750 блоки питания как минимум 450 W. У меня 400 W, поэтому были небольшие опасения, что этого может не хватить. Хватило :) В принципе, даже в минимальные требования производителем в таких случаях закладывается определённый "запас", на случай если у пользователя на БП уже висит несколько HDD, 4 гига RAM и т. д. У меня системник этим необременён, поэтому по этому поводу я особенно не волновался.

А вот мои предыдущие видеокарты:



Слева направо: GeForce 6600 256MB (покупалась изначально с компьютером), Radeon X1300 128MB (приобретена исключительно в целях отладки OpenGL-кода), Radeon HD 2400 256MB (приобретена с целью писать код по Direct3D 10).

Значит установил я драйверы и начал тестировать сэмплы из DirectX 11 SDK.

Тесселяция работает замечательно, провалов в скорости, как это было с GS, нет. Я погонял разные демки (их в SDK 3). Например, чайник в демке PNTriangles11 показывает 1620 fps без тесселяции и 250 fps при уровне тесселяции 19. В примере SubD11 скорость падает быстрее, возможно из-за анимации или просто неоптимально написано. Но в общем при "беглом осмотре" тесселятор от AMD при грамотном программировании и без швыряния ресурсов на ветер - вполне юзабелен.

Запустил я OIT11 и закручинился. 8 fps в малюсеньком окошке.
Потом запустил HDRToneMappingCS11. Там есть опция полноэкранного блюра. в 1280x1024 через Pixel Shader получается 340 fps, через Compute Shader - 11 fps. Проблема явно в драйверах, поэтому с полноценным A-буфером и другими экспериментами в CS пока придётся обождать.

Позапускал ещё DX 10.1 сэмплы, но особенно ничего интересного в визуальном плане они не несут. HDAO этот ни к селу ни к городу, DOF 10.1, выглядящий как УГ... Кто его вообще делал???

Скачал с сайта AMD демки Mecha и Ladybug, попробовал запустить. Mecha не пошла, ругалась красным матом о невозможности создать какие-то ресурсы и т. д, рисовала чёрное окно с одним текстом. Вспомнив о проблемах с драйверами, скачал с сайта AMD декабрьские драйверы под XP/Seven, поставил. Проверил сначала OIT11 - было 8 fps, стало 12. Прогресс. Ладно, будем ждать. Зато Mecha на этот раз запустилась безо всяких проблем и показала попиксельную сортировку во всей её красе без подтормаживаний. Впрочем, fps там не отображался (от греха подальше?), надо бы каким-нить фрапсом замерить. В любом случае, там тоже используется каким-то образом CS, и работает этот driver path хорошо. Дальше я запустил Ladybug... Вау, лучший DOF который я видел! Божья коровка такая милая, ну... ну, как наша Юлька-премьерка. Детализированные текстуры, чтобы был эффект микросъёмки, и DOF на заднем плане. Новый алгоритм называется filter spreading (там в демках есть такой teaching mode, где объясняется что и как). Алгоритм был разработан совместными усилиями инженеров AMD и Калифорнийского университета в Беркли. Глядя на приятные глазу цвета, детализацию и качественный Depth-of-Field, понимаешь, что преимущества нового поколения GPU вполне реальны.

Ну вот, пожалуй, и всё на сегодня.

среда, 23 декабря 2009 г.

Аватар

Посмотрел этот 230 000 000-нный сабж. Фильм прекрасен, уплаченные за билет деньги жалко не будет. Желательно смотреть в 3D (хотя эффект временами спорный), если их есть у вас, конечно :)

P.S. Раздражают ублюдочные кинокритики, в жизни ничего хорошего не создавшие и позволяющие себе критиковать работу Мастера.

вторник, 22 декабря 2009 г.

A-buffer/OIT through SM 5.0

Одним из преимуществ Direct3D 11 является то, что в его рамках появляется возможность решить старую проблему компьютерной графики - вывод прозрачных поверхностей без предварительной сортировки геометрии. Возможное решение - сортировка на уровне пикселей - была недоступна на потребительском железе (FFP или программируемом), вплоть до Direct3D 10.

В Direct3D 10 появилась возможность сортировки ограниченного кол-ва пикселей с использованием stencil-routed K-buffer. Данный метод использует отдельные сэмплы текстуры для хранения данных, кол-во сохраняемых фрагментов ограничиваются максимальным уровнем MSAA. Таким образом в один пиксель можно записать до 8 различных фрагментов (цвета, глубины). Этого явно недостаточно, поэтому метод подразумевает использование дополнительных текстур и нескольких проходов по геометрии, чтобы можно было "захватить" больше фрагментов. Известный пример с зелёным драконом из NVIDIA SDK 10 использует два прохода. Кроме досадной необходимости использовать множество проходов, метод требует подготовки/копирования буфера трафарета для роутинга фрагментов, что тоже бьёт по производительности.

С использованием Compute Shader, тесно интегрированного с остальным графическим API, и поддержкой read/write операций в железе стало возможным реализовать полноценный A-буфер, т. е. буфер, способный аккумулировать неограниченное кол-во значений для одного пикселя (L. Carpenter, 1984). Говоря "неограниченное", я, конечно, подразумеваю под этим словом большое, но всё же конечное кол-во значений, т. к. размер A-буфера на практике лимитирован, ну, хотя бы доступной видеопамятью. Однако досадных ограничений вроде MSAA=8 этот метод не имеет.

В SDK доступен небольшой сэмпл, реализующий Order Independent Transparency (OIT). Рассмотрим, как он работает.

Используются новые объекты HLSL 11: RWTexture2D для операций чтения/записи с двумерными массивами и RWBuffer для операций с одномерными. Работа осуществляется в Pixel и Compute Shader. Сперва прозрачная геометрия рисуется без depth-теста в uint буфер, в котором подсчитывается overdraw для каждого фрагмента изображения:
RWTexture2D fragmentCount : register( u1 );
void FragmentCountPS( SceneVS_Output input)
{
fragmentCount[input.pos.xy]++;
}

(Что интересно, в HLSL 11 у объектов появился operator [], который можно использовать как для чтения значений (вместо Buffer.Load()), так и для записи).

Затем в Compute Shader выполняется так называемый scan получившегося буфера, а точнее - одна из его операций prefix sum. Это хорошо известный и распространённый алгоритм в GPGPU, про него можно почитать в Википедии (Prefix sum) и в GPU Gems 3 (Chapter 39. Parallel Prefix Sum (Scan) with CUDA). Смысл этой операции заключается в том, чтобы за O(n) операций найти для каждого элемента списка сумму предшествующих ему элементов. Например, у нас есть буфер, в котором каждый элемент содержит какое-то заданное значение (в рамках нашей задачи - overdraw):

После parallel prefix sum в Compute Shader получается такой буфер:

Последний элемент содержит сумму всех элементов в буфере.
Prefix sum разбит на два этапа: в первом 2D-текстура копируется в RW-буфер, во втором происходит собственно суммирование. Чтобы подсчитать prefix sum для всего буфера, используется log(N) проходов, т. е. например, для буфера 1024x1024 необходимо 19 проходов. Первое суммирование осуществляется на первом этапе (копировании), поэтому во втором выполняется только log(N)-1 проходов.

Полученные суммы используются для размещения значений цвета и глубины прозрачной геометрии в линейном "глубоком" буфере (deep buffer, или, чтобы следовать придуманной ранее терминологии, A-buffer). Память для deep-буфера конкретно в сэмпле SDK выделяется с расчётом способности хранить до 8 фрагментов на каждый пиксель (это в среднем, в реальности overdraw крайне неравномерен, т. е. для одних пикселей это 0-4, для других 8-10, для третьих 20-30 и т. д. Главное, выделять память таким образом, чтобы по возможности разместились все данные). Размещение происходит в пиксельном шейдере, fragment count буфер перед этим очищается в ноль. Код шейдера:

void FillDeepBufferPS( SceneVS_Output In )
{
uint x = In.pos.x;
uint y = In.pos.y;
uint prefixSumPos = y * FrameWidth + x;
uint deepBufPos;

if (prefixSumPos == 0)
deepBufPos = fragmentCount[In.pos.xy];
else
deepBufPos = prefixSum[prefixSumPos-1] + fragmentCount[In.pos.xy];

deepBufDepth[deepBufPos] = In.pos.z;
deepBufColor[deepBufPos] = clamp(In.color, 0, 1)*255;
fragmentCount[In.pos.xy]++;
}

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

Затем в Compute Shader каждая такая цепочка сортируется bitonic sort-ом, все её элементы смешиваются по альфе, результат записывается в пиксель, соответствующий этой цепочке.

И всё :)

P.S. Существует демо Mecha от ATI, но т. к. утилиты отладки DX11-программ только на подходе, нет возможности добраться до шейдеров этого демо. Позже я обязательно это сделаю и сравню их код с кодом в OIT11.

P.P.S. Кстати, подобный алгоритм А-буфера уже запатентован Microsoft: PREFIX SUM PASS TO LINEARIZE A-BUFFER STORAGE. Впрочем, подобный патент не выдержит ни одного судебного разбирательства, ибо никто не может запретить кому-либо передвигать биты и байты в кристалле так, как ему это заблагорассудится. Знаете о патенте на колесо австралийца Джона Кеога?

воскресенье, 20 декабря 2009 г.

Пров

Поставил безлимитку - ну и провайдер попался! amd.com и nvidia.com блокируют, code.google.com блокирует... Приходится выходить через прокси-сервер.

Не любят нас за кордоном :(

Direct3D 11 and Text Drawing

В 9 и 10 D3DX были классы для вывода текста (конкретно в 10 это ID3DXFont10). Замечательно так работали: задаём параметры шрифта (аналогично структуре LOGFONT), шрифт рендерится в текстуру, потом делаем Font->DrawText() и всё. В Direct3D 11 D3DX совершил очередной round-trip: если при переходе с 9 на 10 Effect FX перекочевал в ядро, то в 11 - обратно в D3DX :) Кроме того, фреймворк доступен в виде исходного кода, а не библиотеки (посмотрев, сколько там говна, я вообще решил не юзать FX). А вместе с изменениями в FX Microsoft убрал такие классы как ID3DXFont10, ID3DXMesh10, ID3DXSprite10 и т. д. Почти ничего этого я не использовал, но вот быстрый вывод шрифта в маленьких аппах через ID3DXFont10 был полезен.

Озадачившись выводом шрифта с новым SDK, я решил выводить его через GDI. Но напрямую GDI и D3D не дружат - текст ужасно мерцает. Полазив по SDK, я нашёл, как делать правильно.

1) При создании swap chain указываем флаг DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE.
2) Формат back-буфера должен быть DXGI_FORMAT_B8G8R8A8_UNORM_SRGB или DXGI_FORMAT_B8G8R8A8_UNORM (порядок RGBA не поддерживается).
3) Количество отсчётов в back-буфере должно быть равным 1.
4) Далее необходимо получить интерфейс поверхности (новый интерфейс в DXGI 1.1):

IDXGISurface1* pSurface1 = NULL;
pSwapChain->GetBuffer(0, __uuidof(IDXGISurface1), (void**)&pSurface1);

У этого интерфейса есть методы GetDC() и ReleaseDC(). Вывод текста средствами GDI можно делать между этими двумя вызовами:

HDC hDC;
pSurface1->GetDC(FALSE, &hDC);
...
//Draw on the DC using GDI
...
//When finish drawing relase the DC
pSurface1->ReleaseDC(NULL);

5) После необходимо заново прицепить render target окна, т. к. он отвязывается. У меня, если этого не делать, рантайм падает в D3D11SDKLayers.dll.

пятница, 18 декабря 2009 г.

Direct3D 11 Inspection

Нашёл несколько интересных мест в Direct3D 11.

Первое - D3D11_DEPTH_STENCIL_VIEW_DESC. У этой структуры появилось дополнительное поле UINT Flags (в 10 версии его не было). Я обнаружил это, когда прямой копипаст DX10-кода в DX11 не заработал (в дополнительном поле был мусор и рантайм возвращал ошибку). В SDK по этому поводу написано следующее:
A value that describes whether the texture is read only. Pass 0 to specify that it is not read only; otherwise, pass one of the members of the D3D11_DSV_FLAG enumerated type:

D3D11_DSV_READ_ONLY_DEPTH
Indicates that depth values are read only.
D3D11_DSV_READ_ONLY_STENCIL
Indicates that stencil values are read only.
В пояснении написано:
Limiting a depth-stencil buffer to read-only access allows more than one depth-stencil view to be bound to the pipeline simultaneously, since it is not possible to have a read/write conflicts between separate views.
Я так понимаю, это сделано для того, чтобы можно было одну и ту же depth/stencil текстуру одновременно прицепить как render target на запись и как shader resource view на чтение в шейдере, при этом разбросав чтение и запись по depth/stencil. Иначе непонятна формулировка "allows more than one depth-stencil view to be bound to the pipeline simultaneously", т. к. методы ID3D11DeviceContext::OMSetRenderTargets() и ID3D11..Context::OMSetRenderTargetsAndUnorderedAccessViews() принимают только ОДИН depth/stencil view. (кстати, о последнем методе: в SDK опять напортачили секретарши майкрософта, типы параметров отличаются от таковых в D3D11.h). К тому же неясно, зачем вообще подключать одновременно два depth/stencil view (буфера) к пайплайну. Получается, речь идёт именно о OM/PS конфликте. Но и в этом свете приведённая формулировка туманна, т. к. PS принимает именно resource view, а не depth-stencil view.

В общем, как-то всё туманно и неясно, как конкретно использовать новое преимущество. Пишем в depth, а в шейдере читаем только stencil из того же самого буфера? Пишем в stencil, в шейдере читаем только depth?

Ещё один интересный найдёныш из Direct3D 11, на этот раз в HLSL attributes. Помимо прочих, появился новый атрибут earlydepthstencil. Назначение - ФОРСИРОВАТЬ выполнение early z/stencil тестов до пиксельного шейдера. Как известно, железо может это делать только при соблюдении определённых условий (например, из шейдера не пишется глубина), а если они нарушаются, то early тесты отключаются.

Кстати, вот мои мысли, высказанные два года назад по этому поводу:


Так что мои мысли оказались в каком-то роде пророческими. Интересно, насколько новый атрибут устойчив к различного рода пакостям? Например, если попытаться писать в глубину, кто кого сломает? :)

пятница, 11 декабря 2009 г.

Inverse in PS

Я ждал, когда это случится! И наконец-то это произошло. Передо мною встала простая задача - инвертировать матрицу 3x3 в пиксельном шейдере.

Update

Нашёл в Сети и протестировал, похоже, оптимальный вариант.
Вот код: invert. Проверки det == 0 нет, для PS это роскошь.

вторник, 8 декабря 2009 г.

Пуф-ф-ф!

Всё, сдулся Larrabee...

воскресенье, 6 декабря 2009 г.

Всё, что имеет начало, имеет и конец

Наткнулся на запись боя между Дэнни Грином и Роем Джонсом младшим, который прошёл на днях:

Danny Green vs Roy Jones Jr from Sydney, Australia 02/12/09

Очень грустное зрелище, конечно. Вот так заканчивают Великие Атлеты (действительно великие), если не знают, когда пора уйти со сцены (или же не хотят этого делать).

пятница, 4 декабря 2009 г.

Ньютон

Я иногда перечитываю биографии великих физиков и математиков, стараясь понять, какие личностные качества помогали им добиваться успеха. Очень интересным оказался один случай из биографии Исаака Ньютона (вычитал в Википедии):

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