воскресенье, 9 декабря 2012 г.

New CG Books

Две недели назад заказал вот такие книжки на Amazon и к назначенной дате мне их прислали:



Must have в библиотеке любого, кто увлекается компьютерной графикой. Со временем планирую коллекцию книг пополнять.

четверг, 6 декабря 2012 г.

Renaissance

Посмотрел "Ренессанс" Кристиана Волькмана - и этот стильный чёрно-белый нуар мне очень понравился - чем-то напоминает рисованные комиксы из 90-х. Возникла идея немного повозиться с cel-shading-ом, который до этого я обходил стороной, а теперь понял, что визуальные образы, созданные с его помощью, могут быть интересны глазу. Возможно выкрою время и до Нового Года успею сделать демо.


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

No Country for Old Men

Один из сотрудников Microsoft заявил, что планов по выпуску DirectX 11.1 для Windows 7 нет:

суббота, 10 ноября 2012 г.

Vogue


Ladies with an attitude
Fellows that were in the mood
Don't just stand there, let's get to it
Strike a pose, there's nothing to it

четверг, 4 октября 2012 г.

Peugeot 308 SW

Я стал обладателем новенькой машины, универсала Пежо 308 (автомат, модель 2012 года).

Для человека, который до сих пор ездил в вагоне метро, это значительное событие - поэтому вчера обмывал покупку бутылкой красного вина. Теперь стоит задача регистрации авто в ГАИ (благо оно упростилось в этом году, спасибо Виктору Фёдоровичу) и собственно обкатка машины - в 5 утра на дорогах Киева почти нет транспорта, и можно устраивать лёгкие покатушки, чтобы прочувствовать характер машины, габариты и т. д. Этот железный друг обошёлся мне приблизительно в $24000, + 3% (от стоимости без НДС) уплата в Пенсионный фонд при регистрации (по украинскому законодательству).

четверг, 13 сентября 2012 г.

iPhone 5

To use a car analogy, six years ago the iPhone was like a sexy new flagship model from BMW or Porsche. Today it's a Toyota Camry. Safe, reliable, boring. The car your mom drives. The car that's so popular that its maker doesn't dare mess with the formula.

среда, 12 сентября 2012 г.

Всех с Днём программиста!

Tower 101

Был две недели в командировке в Samsung Ukraine.

Они арендовали 15000 кв. м в новом бизнес-центре Tower 101. Эксперты называют эту сделку крупнейшей на рынке офисной недвижимости Украины. Офисы у Самсунга просторные, четыре этажа (у меня доступ был только на один этаж). Скоростные лифты, бесплатные обеды, нормальное железо :)

среда, 22 августа 2012 г.

Driving Licence

Ну вот, после 5-ти месяцев мучений я стал обладателем водительских прав:


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

Осторожно – автошкола! или Как получают права
И вот ещё:
Автошкола

В самом конце, после оплаты 249 грн (~30$) в кассе МРЭО (а в прошлом году было дороже!), мне выдали:
  • Водительское удостоверение
  • Экзаменационную карточку водителя
  • Свидетельство об окончании автошколы
  • Копию мед. справки
P.S.

Учите билеты!
И не ждите "покращень", а готовьте трактор :)

пятница, 10 августа 2012 г.

I leave Ubisoft

Сегодня был мой последний день в Ubisoft Kiev.
:(

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

Direct2D interop

Всё началось с того, что из Direct3D 11 исключили классы наподобии ID3DXFont для работы с текстом. Это очень неудобно для маленьких приложений, когда некогда писать собственный фреймворк для текстурных шрифтов, а вывести простейший текст надо.

Как быстрое решение, можно выводить текст, используя старый добрый software GDI API, указав флаг DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE при создании swap chain-а, но это решение серьёзно роняет fps - чем больше размеры бэк-буфера, тем дольше выполняются методы GetDC/ReleaseDC, т. к. происходит трансфер буфера из видеопамяти.

С выходом DirectX 11 Microsoft представила новые API на замену GDI/GDI+ - это Direct2D и DirectWrite, которые подразумевалось использовать в том числе и для вывода текста. Эти API используют преимущества аппаратного ускорения, но беда в том, что сами они были написаны на базе Direct3D 10.x, и поэтому несовместимы с интерфейсами Direct3D 11. Подробнее об этом можно прочитать здесь: Hacking Direct2D to use directly Direct3D 11 instead of Direct3D 10.1 API.

Решение этой проблемы, представленное там, мне понравилось, но я был не удовлетворён его реализацией - она написана на скорую руку, как proof of concept. Решение построено на оборачивании интерфейса IDXGISurface в промежуточную оболочку, которая подставляется вместо реального интерфейса. Я решил займствовать идею, и создал свою реализацию proxy-интерфейсов, заключив всё в отдельную dll. Чтобы понять, какие вызовы Direct3D делаются из Direct2D, каждый метод логируется и проверяются коды возврата (HRESULT).

Это решение отлично работает в независимом приложении, но при попытке отладить его в PIX происходит крэш (на вызове D3D10CreateStateBlocks). Сложно сказать, из-за чего именно - возможно, на уровне d3d10.dll используются какие-то недокументированные особенности COM-интерфейсов Direct3D 10, которые не учитываются в proxy-интерфейсах. Также грядущий DirectX 11.1 содержит обновление Direct2D 1.1, которое работает с последней версией Direct3D API, делая хак несколько устаревшим. Впрочем, если приложение должно работать на чистой Windows 7 (которая будет жить ещё долго и счастливо), решение может оказаться очень полезным.

Update Aug 21, 2012

Обнаружил, что использование библиотеки приводит к утечке памяти - не все созданные объекты корректно удаляются, поэтому Direct3D debug layer рапортует о "live objects" после удаления устройства. Проанализировал код и дописал ->Release() к некоторым объектам. Также от меня ускользнуло то, что Direct2D пытается запросить интерфейс ID3D10InfoQueue, поэтому добавил обёртку и для него (хотя это не обязательно). В конце упростил функцию D2DXCreateInteroperableSurface() - теперь она принимает всего два параметра, а интерфейсы устройства и контекста получает через методы API. Поправил инкапсуляцию в классе-обёртке устройства Direct3D.

Ссылки на исходный код на Google Code:
d2dx.zip
d2d_interop.zip

пятница, 3 августа 2012 г.

Drive

Один из трэков к фильму "Drive", который мне ну очень понравился.

понедельник, 9 июля 2012 г.

Brave



Идти. Однозначно. В ТРИДЭ.

суббота, 7 июля 2012 г.

Render Target Compression

Некоторое время назад я заинтересовался компрессией текстур на лету. Типично в DXT (BC в D3D 10/11) формат преобразуются только статические текстуры, подготовленные художниками. Однако повсеместно в кишках графического движка семплируются и текстуры, которые являются динамическими render target-ами, и если кол-во выборок в пиксельном шейдере значительно, имеет смысл попробовать после отрисовки сжать такую текстуру (закрыв глаза на деградацию качества), и семплировать в сжатом формате.

В DirectX 10.0 такой возможности ещё не было, но до неё был буквально один шаг! Direct3D 10.0 позволял копировать в памяти только ресуры с одинаковым typeless форматом, например текстуру формата R32G32B32_FLOAT в текстуру формата R32G32B32_UINT. Direct3D 10.1 снял это ограничение и стало возможным копировать ресурсы с разными форматами при условии, что их размеры в памяти совпадают. Подробнее описано в разделе MSDN Format Conversion Using Direct3D 10.1.

Базой для моей работы послужило демо Хумуса GPU Texture Compression. Основной механизм преобразования формата прост: изображение из render target перебрасывается в текстуру формата R32G32_UINT, пиксельный шейдер используется для упаковки данных таким образом, чтобы после копирования в block compressed формат они интерпретировались как блоки 4x4, готовые к декомпрессии. Мне необходимо было сжать одноканальную текстуру формата R8_UNORM, поэтому в качестве целевого целесообразнее всего использовать формат BC4 (появился в DirectX 10.0). Разобравшись с демкой Хумуса, я немного оптимизировал её: например, он вычисляет текстурные координаты для gather выборок в вершинном шейдере, хотя для этого можно использовать опциональные целые смещения. Для ремаппинга в индексы блока 4x4 можно использовать индексируемый массив вместо замысловатого тернарного оператора (кол-во инструкций в шейдере уменьшается). Наконец используется статическая картинка большого размера, когда отличить сжатое изображение от оригинала невозможно - я же использовал динамический рендер в текстуру и последующее её сжатие.


Также разобравшись, как GPU осуществляет декомпрессию формата BC4, я написал экспериментальный шейдер, который в процессе упаковки пытается подобрать оптимальные индексы из таблицы, которую GPU строит по двум опорным значениям. Увы, качество сжатия едва ли отличимо, а вот кол-во инструкций в шейдере получилось на порядок больше. Впрочем, это была лишь попытка понять, как работает декомпрессор.

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

Ссылка на демо с исходным кодом на Google Code:
renderbc.zip

пятница, 6 июля 2012 г.

воскресенье, 24 июня 2012 г.

Microsoft Surface

Стивен Синофски облажался с презентацией новой разработки Microsoft. Что-то я не припомню, чтобы Джобс лажал вот так:

понедельник, 28 мая 2012 г.

Soft Shadows Again

Новое демо в разработке:


вторник, 22 мая 2012 г.

La Boum

Facebook

Нет, это пост не о том, как Цукерберг стал миллиардером после IPO. И не о том, как он сошёл с ума и женился на Присцилле Чан :) Просто Ubisoft Kiev завёл свою страничку в социальной сети:


четверг, 3 мая 2012 г.

ПДД или ДАЙ

Последнюю неделю я яростно штурмовал наше украинское ПДД, т. к. на носу уже висел экзамен в автошколе, а до этого учиться было всё некогда :)

Взял на работе отпуск, рассовав vacation days между майских праздников. Отключил интернет. С восьми утра до двух ночи (с перерывами, ес-но) я решал главы, билеты, читал и перечитывал ПДД, ОБД, право, первую помощь. И так каждый день. Мозг стал запоминающим устройством, глаза превратились в устройства ввода. Сутки как период перестали существовать, вместо этого минуты и часы слились в один сплошной поток, который медленно приближал меня к минуте славы начала экзамена. В этом потоке я иногда вылезал из дому в магазин, иногда спал (когда вдруг темнело), а во всё остальное время поглощал ПДД, дабы потом как можно реже сталкиваться с сотрудниками ГИБДД (или по-хохляцки - ДАЙ), взяв первый форпост - автошколу.

Чёрт побери, я снова почувствовал себя студентом :)

суббота, 21 апреля 2012 г.

Lexus от Бога



21. Иисус сказал ему: если хочешь быть совершенным, пойди, продай имение твое и раздай нищим; и будешь иметь сокровище на небесах; и приходи и следуй за Мною.
22. Услышав слово сие, юноша отошел с печалью, потому что у него было большое имение.
23. Иисус же сказал ученикам Своим: истинно говорю вам, что трудно богатому войти в Царство Небесное;
24. И еще говорю вам: удобнее верблюду пройти сквозь игольные уши, нежели богатому войти в Царство Божие.

от Матфея, гл. 19.

пятница, 13 апреля 2012 г.

Batch Drawing

Я решил потратить время и написать отдельную демку, которая бы показывала преимущество от рисования сцены батчами над традиционными методами рисования, в Direct3D 11. Общая идея уже была описана ранее, теперь дело стало за концептом.

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


Для простоты я взял один геометрический объект - Box, и размножил его в массив. На самом деле, как я уже показывал, можно использовать самую разную геометрию (если топология примитивов постоянна), но в данном случае это не принципиально. Вершины и нормали бокса хранятся в формате half, индексы - в ushort, при объединении в один буфер индексы переводятся в формат uint. Массив боксов имеет размеры 32x32 (1024 объекта), т. к. это максимальное количество матриц размерностью 4x4, которое может быть размещено в одном буфере констант (ограничение SM 4.0). Можно взять и больше, использовав, например, Buffer из SM 5.0 или даже текстуру для хранения констант, но я остановился на самом простом варианте. 

Массив боксов рисуется дважды - один раз в Z-only pass, затем в diffuse pass, т. е. делается 2K draw call-ов. Базовый способ отрисовки - по-объектный; оптимизированный - без изменения render states; наиболее быстрый - одним вызовом ID3D11DeviceContext::DrawIndexed(). Все они уже были описаны мною ранее.

После того, как была написана первая рабочая реализация, стало понятно, что в оптимизированных вариантах программа упирается уже не в API, а в расчёт матриц трансформации, т. е. всё равно в CPU. Например, в цикле для каждого объекта нужно рассчитать WVP матрицу и записать её в буфер констант:

XMMATRIX rot = build_rotation(p, y, r);
XMMATRIX trans = build_translation(x, y, z);
XMMATRIX world = rot * trans;
XMMATRIX world_view_proj = world * precomputed_view_proj;

Этот код CPU-bound, несмотря даже на то, что используются SSE-интринсики из XNA Math. Его оптимизация превратилась в увлекательное занятие. Первое, что можно тут сделать - не перемножать матрицы поворота и переноса, а просто записать вектор переноса в четвёртую строку матрицы поворота. Второе - не умножать на матрицу вида-проекции, а записывать в буфер констант сразу матрицу преобразования в мировые координаты. Матрица вида-проекции записывается в отдельный буфер констант в начале кадра, а в вершинном шейдере вместо одного умножения на матрицу последовательно выполняется два. Далее, в XNA Math матрица поворота из трёх углов на самом деле строится в два приёма: сначала строится кватернион поворота, а затем он конвертируется в матрицу 4x4, что неоптимально. Я решил записывать в константный буфер этот самый кватернион + вектор переноса, а в шейдере умножение на матрицу заменить умножением на кватернион и сложением с вектором переноса. Матрица 4x4 занимает 64 байт, тогда как пара кватернион/вектор занимает 32 байт, т. е. через шину PCI передаём в два раза меньше данных. В шейдере умножение на кватернион занимает 8 инструкций dxasm против 4-х для матрицы. Т. е. мы последовательно разгружаем CPU за счёт увеличения нагрузки на вершинный шейдер, но т. к. изначально программа CPU-bound и нужно сместить workload, то все эти методы оправданы. К слову, общее кол-во вершин в демке невелико и раздувшийся вершинный шейдер тут слабо влияет на скорость работы. В реальном случае со сложной геометрией потребление ресурсов GPU конечно будет расти быстрее.

После всех оптимизаций расчёт трансформаций перестал быть "бутылочным горлышком", даже использование кватернионов не дало значительного эффекта :) Это значит, что программа стала "упираться" в пересылку констант на GPU. В кавычки взято потому, что затрачиваемое время никак нельзя назвать значительным для такого кол-ва констант. Наиболее ощутимым эффект от оптимизации проявился на слабых системах, для примера возьмём мой ноутбук (процессор Intel® Celeron® M Processor ULV 723, видео AMD Radeon HD 4330):

per-object: 9.3 ms
w/o render state alternation: 1.85 ms
one batch: 1.33 ms
one batch w/ quaternions: 1.33 ms

Т. е. получилось ускорить вывод почти на порядок! Между вторым и третьим способом различия в производительности заключаются в десятых долях миллисекунды, что свидетельствует о том, что DIP cost в рантайме/драйвере незначителен в случае отсутствия изменений на конвейере. На мощных системах (процессвор Intel Core i5, видео GeForce GTX 560) выигрыш не так заметен, т. к. изначально даже неоптимизированный вариант занимает меньше 2 ms - нужна более сложная сцена:

per-object: 1.32 ms
w/o render state alternation: 0.34 ms
one batch: 0.19 ms
one batch w/ quaternions: 0.17 ms

Ссылка на демо: batchdraw.zip

Доступны два exe, один выполняется в окне, другой - в полноэкранном разрешении 1280x720.
Отрисовка текста сжирает большую часть времени, поэтому его можно прятать клавишей T.

понедельник, 9 апреля 2012 г.

У

Ох уж эта педаль сцепления!


суббота, 31 марта 2012 г.

DIP Cost Reduction

Часто такие способы отображения сцены как forward rendering и deferred lighting подвергаются критике за необходимость отрисовывать одну и ту же геометрию несколько раз. Например, в DL требуется отдельно заполнить depth/normal буфер, что увеличивает DIP count в два раза по сравнению с DS. В forward rendering-е практически обязательно делать отдельный early Z-pass... При большом кол-ве вызовов функции Draw*() программа легко может стать CPU-bound. Особенно это критично при портировании DirectX-программ на какой-нибудь Mac, где драйверы OpenGL значительно хуже оптимизированы для большого кол-ва вызовов.

Я задумался над оптимизацией early Z-pass с точки зрения нагрузки на CPU. Сразу необходимо отметить, что я не рассматриваю варианты, где возможно применение инстансинга, речь пойдёт о выводе различной геометрии с топологией triangle list. Ключевым преимуществом является то, что в early Z-pass  изменения render states минимальны, типичный сценарий отрисовки примерно такой:
for each object
{
UpdateMatrix();
SetVertexBuffer();
SetIndexBuffer();
DrawGeometry();
}
Легко видеть, что между вызовами Draw() меняются только константы (матрица WorldViewProj) и вершинный/индексный буферы. Это можно оптимизировать, если объединить все геометрические данные в одном буфере. В Direct3D 10/11 с помощью параметров StartIndexLocation и BaseVertexLocation метода ID3D11DeviceContext::DrawIndexed() можно управлять, какую порцию вершинных данных необходимо отрисовать. Тогда оптимизированный вариант может выглядить так:
SetBigVertexBuffer();
SetBigIndexBuffer();
for each object
{
UpdateMatrix();
DrawGeometry();
}
Как мешают эти матрицы! А что, если обновлять матрицы не поодиночке, а сразу массив матриц для всех объектов? Тогда возникает вопрос, каким образом применить трансформацию индивидуально к каждому объекту? К сожалению, в Direct3D из шейдера невозможно узнать номер вызова отрисовки (по примеру SV_InstanceID в режиме инстансинга), но можно ценой дополнительных усилий организовать это вручную. Допустим, нам нужно отрисовать три объекта, каждый из которых имеет 3, 4 и 8 вершин. Создадим дополнительный вершинный буфер, атрибуты которого содержат ID объекта. Если мы объединим геометрию последовательно, то дополнительный буфер должен иметь вид:
000111122222222
Если подсоединить этот буфер на дополнительный IA-слот, то в шейдере станет возможным выбирать матрицу трансформации по ID из этого буфера. Теперь оптимизированный вариант рендеринга будет выглядить так:
UpdateMatricesForAllObjects();
SetBigVertexBuffer();
SetMeshIDVertexBuffer();
SetBigIndexBuffer();
for each object
{
DrawGeometry();
}
В итоге, в цикле остались только непосредственно вызовы метода рисования. Direct3D runtime оптимизирован в том смысле, что DIP cost зависит от количества изменений на конвейере, т. е. чем меньше изменений с момента предыдущего DIP, тем дешевле следующий за ним. В последнем варианте наиболее тяжёлым будет только первый вызов, все остальные - значительно легче (насколько именно - зависит от вендора и версии драйвера).
Присутствие цикла объясняется тем, что в кадре обычно видна только часть всех объектов сцены, поэтому необходимо определять их видимость из виртуальной камеры, заносить в отдельный список и рисовать объекты только из этого списка. Если специфика игры такова, что все объекты видны в камере, цикл можно заменить единственным(!) вызовом, подготовив отдельный буфер индексов и указав IndexCount для всех объектов. Если от цикла нельзя избавиться, можно попробовать сократить кол-во его итераций: например, если какие-то объекты видны в камере и одновременно лежат в буфере линейно, вызовы отрисовки для каждого из них можно объединить в один (этакий DIP-merge).

Для практической проверки всех этих мыслей на коленке была написана демка. Она показывает четыре объекта с разной геометрией, каждый из которых имеет свою трансформацию:


Ссылка на демо: dipreduc.exe

P.S. Поразмышлять на тему оптимизации меня побудил следующий вброс: DX11 vs GL Driver Overhead
где просто предлагается сделать 100000 DIP-ов чтобы прочувствовать мощь расширений NVidia :)

понедельник, 26 марта 2012 г.

Fast Ray-BV Intersection

Когда-то давно, ещё в 2009, я написал несколько функций для теста пересечения луча и ограничивающего объёма (сфера, эллипс, коробка). Эти функции могут быть полезны, например, для оптимизации трассировки лучей в пиксельном шейдере. Идея была в том, чтобы реализовать действительно оптимальные варианты функций для каждого ограничивающего объёма. Спустя несколько лет я вернулся к этим кодам и после тщательного анализа мне удалось найти способ сократить эти функции ещё на одну-две инструкции! Теперь я уверен, что реализованный функционал действительно близок к эталонному. Тогда же я решил сделать реализацию open source, т. к. сам в прошлом потратил много времени на гугл, пытаясь найти что-нибудь подходящее - увы, многие коды были громоздкими, неоптимальными и часто даже не на С/С++.

Я реализовал функции проверки пересечения луча со сферой, AABB, эллипсом и OOBB. В двух последних, более сложных случаях, используется простой трюк, при котором луч трансформируется в локальное пространство ограничивающего объёма, используя заранее просчитанную матрицу.

Кстати, если сможете написать оптимальнее - дайте знать! :)
Ссылка на пример с исходным кодом на Google Code:

raybv.zip

Удачного рэйтрейсинга!

вторник, 13 марта 2012 г.

Crinkler Edition

Обновил немного код своих демок: некоторые из них не компилировались c однобайтовыми (char) строками, были другие мелкие погрешности. Из Release конфигурации решено было исключить CRT и всякие buffer security checks, т. к. реально из этого почти ничего не использовалось, а довесок в .exe получается существенным - кода из CRT вставлялось больше, чем было самого кода программы.

Также я немного поигрался с разными упаковщиками .exe файлов и выбрал Crinkler, как наиболее эффективный. Он работает на стадии компоновки и может быть использован вместо стандартного студийного link.exe. Теперь размеры .exe файлов демок занимают не 20-30 Кб, а 3-10 кб. На слабых системах с HDD диском такие программы "на глазок" стартуют легче и быстрее.

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

четверг, 8 марта 2012 г.

вторник, 28 февраля 2012 г.

пятница, 17 февраля 2012 г.

Bicubic Texture Filtering

Всю последнюю неделю я занимался программируемой фильтрацией текстур. А началось с того, что я решил из спортивного интереса не преобразовывать .pcx текстуры из Quake II в RGBA (моветон) и не пережимать их в BC (с потерей деталей, которых и так почти нет) а загружать  в видеопамять таблицу индексов и палитру и делать выборку из палитры прямо в шейдере. Всё бы хорошо, но при таком раскладе нельзя использовать стандартную фильтрацию текстур, т. к. в этом случае будут усредняться индексы палитры, а не сами цветовые значения, что является ошибкой. Возможное решение - сначала делать выборку из палитры, а потом вручную выполнять билинейную фильтрацию.

Решено - пишем собственную билинейную фильтрацию (а заодно и бикубическую). У NVIDIA есть .fx файл с реализацией бикубической фильтрации и пример с реализациями различных фильтраций на CUDA. Взяв их за основу и прочитав кое-какую информацию о бикубическом ресемплинге, я написал собственные реализации.

В кубической фильтрации используется формула Митчелла-Нетравали, сплайном можно управлять с помощью двух параметров - B и C. Наиболее правильная фильтрация - по Митчеллу, с параметрами B=1/3, C=1/3, но параметры можно подбирать и произвольно. Ступенчатость лучше всего сглаживается при параметрах B=1, С=0, ценой "замыливания" изображения. NVIDIA использует иную формулу, которая идентична B=0, C=3/4 при A=-3/4 (см. GPU Gems).


Некоторые заметки о ходе разработки.

Для билинейной фильтрации необходимы четыре выборки, но если использовать Gather4 из sm 5.0, можно обойтись только тремя (для RGB компонент). При сравнении аппаратной билинейной фильтрации с реализованной вручную выявилось интересная особенность: похоже, все видеокарты AMD выполняют билинейную фильтрацию со слегка сниженной точностью, т. к. на экстремально маленьких текстурах заметен бандинг. Оптимизация в принципе оправдана, т. к. на больших по размеру текстурах сниженная точность незаметна. На NVIDIA отличий между аппаратной и программной реализациями нет.

При отладке бикубической фильтрации выявились странные различия в семплировании текстур с точечной выборкой между всеми видеокартами и Reference Rasterizer. Они проявлялись в виде паразитных линий, идущих параллельно границам текселей текстуры. Первоначально отлаженный на GTX 560 шейдер работал неправильно на HD 4330, HD 48xx и HD 68xx и refrast-e. Чтобы исправить работу под старшими моделями AMD и refrast-ом, экспериментальным путём было установлено, что из текстурных координат нужно вычесть смещение в 1/512, а для серии HD 4xxx - смещение в 1/128. Само по себе это странно, и сами числа наводят на мысли - это первый случай, когда я наткнулся на такие различия. С этим надо что-то сделать. Попытка отладки шейдера в PIX-е показала, что PIX показывает финальный output пиксельного шейдера, отличный от того, что записан в render target. Приехали.

Текущая реализация бикубической фильтрации использует 16 выборок на пиксель. В CUDA-примере от NVIDIA показано, что можно уложиться в четыре выборки, но пока неясно, насколько это "честно". Есть куда копать.

И наконец, почему бикубическая фильтрация до сих пор не реализована аппаратно? (Ok, похоже, что реализована, но D3D11 доступ к ней не предоставляет). Ответ прост: она требует значительно больше вычислительных ресурсов, при этом не даёт заметного преимущества в качестве при использовании текстур большого размера. Т. к. все современные видеокарты обладают значительным количеством локальной видеопамяти, большие текстуры используются повсеместно. Впрочем, для RAGE Кармак обещал бикубическую фильтрацию (там некоторые текстуры неоправданно пожаты).

Ссылки на примеры с исходным кодом на Google Code:

cubic-plot.zip
bicubic.zip

Update Feb 25, 2012

Переписал шейдеры таким образом, чтобы семплирование текстуры осуществлялось через метод Texture.Load() вместо point-семплирования. В этом случае GPU работает чётко с целочисленными адресами, что, как и ожидалось, привело к отсутствию ошибок и идентичному результату на всех видеокартах.

bicubic-2.0.zip

Google Code

Заметил что в сабже начали использовать Bitstream Vera Sans Mono при браузинге кода. Единственный нормальный шрифт для программирования.

среда, 15 февраля 2012 г.

HAL vs Reference Rasterizer

Я столкнулся с трудным багом - реализация семплирования текстуры с nearest фильтром в видеокартах GeForce GTX 5xx отличается от такового в Microsoft Reference Rasterizer. А все современные Radeon-ы, на которых я запускал тест, показывают картинку, идентичную RefRast-y.

И самое интересное - эти отличия в GeForce приводят к визуально правильному результату, а во всех остальных случаях - проявляются артефакты. Даже отладчик шейдеров в PIX бажит :(

четверг, 9 февраля 2012 г.

Books

Купил пару новых книг:


Думаю, в описании они не нуждаются.

воскресенье, 5 февраля 2012 г.

Visual Studio Vista

10 и 11 студии ужасны. Точка.

суббота, 28 января 2012 г.

пятница, 27 января 2012 г.

iMac Greyish Smudge

Мой iMac 27' уже где-то полтора года в эксплуатации. А сегодня я заметил на его матрице, в правом верхнем углу, странные серые разводы. Я подумал что на фронтальном стекле скопились пыль и жир и протёр его влажной салфеткой. Но оказалось, что это повреждения IPS матрицы, а не грязь на стекле.

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

Думаю, через два-три года там образуется порядочное серое пятно :(

понедельник, 23 января 2012 г.

DirectGL progress

Завершил работу над функциями по загрузке 2D-текстур. Решено реализовать только загрузку BMP, TGA и DDS форматов (без поддержки расширений DX10, т. к. они всё равно не применимы в DX9-рендере). Я написал простой unit test который грузит DDS текстуры разных форматов (как сжатые, так и различных форматов вида RGB8, R5G6B5 и т. д.), а фактически текстуры разделены на два лагеря - несжатые RGBA8 и сжатые DXT, всё остальное практически никогда не используется для хранения в видеопамяти. Для полноты необходимо реализовать ещё загрузку cubemap текстуры из DDS, но это если руки дойдут.

Решено также постепенно подключать к проекту различные unit test-ы, как это практикуется в Wine, чтобы тестировать враппер на соответствие реальному поведению рантайма Direct3D. Из того же Wine эти тесты и думаю заимствовать (как-никак open source).

Также заставил работать через враппер два сэмпла из DirectX 9 SDK - EmptyProject и SimpleSample. Как и другие примеры они интенсивно используют DXUT, так что это хороший тест "на прочность". Но тут обнаружился интересный баг - шрифт рисуется в бэк-буфер перевернутым по оси ординат:


Тут нужно отметить, что при портировании Direct3D кода на OpenGL есть один неприятный момент:  начало системы текстурных координат Direct3D находится в левом верхнем углу, тогда как в OpenGL - в левом нижнем. Для обычных текстур это не имеет значения, т. к. похоже драйвер правильно укладывает их в память (или железо вычисляет адрес в памяти) в зависимости от используемого API. Но с render target текстурами при их семплировании в шейдере приходится инвертировать текстурные координаты по оси Y. Это решение имеет по крайней мере два недостатка:

1) Необходимо пересмотреть код всех шейдеров и в тех местах, где семплируется render target, поправить текстурные координаты.

2) Возможны случаи, когда в шейдер на один и тот же текстурный слот попеременно передаётся обычная текстура и render target. Тут приходится заводить дополнительный булевый флажок либо создавать отдельный шейдер для каждого случая. При использовании флажка, если он по какой-то причине не отражает реальное положение дел (упустили где-то, энджин-то большой), изображение нарисуется перевёрнутым.

Оба эти решения являются неприемлемыми.

Как компромисс, мною решено менять матрицу проекции так, чтобы растеризуемая геометрия рисовалась в render target с инвертированием по оси ординат. Позже, при семплировании этого render target в шейдере, произойдёт восстановление правильного изображения. Обычно в коде достаточно мало мест, где устанавливается матрица проекции для вершинного шейдера (или TnL), или мы можем знать номер регистра, по которому лежит первая строка (столбец) матрицы, поэтому внести поправки в этом случае легче, чем в остальных.

Если же использовать этот метод, то любые другие способы записи в render target (например, отображение шрифта силами GDI), не проходят преобразование матрицей, и попадают в render target с исходными координатами - соответственно при семплировании в шейдере изображение перевернётся. Впрочем, это минорный недостаток, в реальных играх вся графика полигональна.

Общий вид GDI текста с инвертированием текстурных координат:


Как видно, теперь перевернулись элементы интерфейса, нарисованные с помощью полигонов. Надеюсь, дилемма понятна.

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

четверг, 19 января 2012 г.

понедельник, 16 января 2012 г.

Фобос-Грунт

Сабж наконец-то упал в океан.
Нанотехнологии блять!

четверг, 12 января 2012 г.

30

Вот так вот живёшь, крутишься, чего-то добиваешься...
А тут тебе, хлоп - тридцать лет стукнуло :)

пятница, 6 января 2012 г.

Stereo Texture

Пока мы все вынуждены ждать Direct3D 11.1, где появится нэйтивная поддержка стереоскопии, под DirectX 9 для всяких этих ваших ассассинов кридов приходится использовать различные хаки, чтобы улучшить поддержку стерео.

Механизм, через который работает 3D Vision на картах NVidia, довольно прост - драйвер добавляет footer в каждый вершинный шейдер таким образом, чтобы позиция вершины в clip space различалась для правого и левого кадра (см. NVIDIA 3D Vision Automatic Best Practices Guide). Таким образом стерео автоматически работает в большинстве трёхмерных игр и даже тех, которые изначально под него не разрабатывались.

Проблемы начинаются в случаях, если при рендеринге использовались какие-то нечестные техники или в случае различных deferred подходов - картинка обычно выглядит неестественно, если не сказать хуже. 3D Vision работает на уровне геометрического пайплайна, во всех остальных случаях нужно что-то делать руками самостоятельно. Вот недавно по работе мне было поручено исправить некоторые огрехи нашего движка при работе в стерео-режиме. Как выяснилось, одна из техник использовала механизм отложенного рендеринга партиклов, при котором цвет и дополнительная информация сначала рисуются в промежуточный render target, а потом изображение из него "вклеивается" в основной кадр. Понятно, что 3D Vision проплывает в этом случае по левому борту, и необходимы поправки уже в пиксельном шейдере. Но вот загвоздка - стандартными средствами Direct3D выяснить какой из кадров - правый или левый, рисуется в данный момент - в шейдере невозможно, а без возможности их различать нет и возможности что-либо изменить.

Начав искать в Сети методы решения подобных проблем, я обнаружил, что NVidia предоставляет пример StereoIssues в NVIDIA Graphics SDK 11, в котором имеется код по созданию специальной "стереотекстуры" (файл nvstereo.h). Стереотекстура позволяет семплировать в шейдере данные, которые будут различаться для левого и правого кадра, так что, например, можно вычислить смещение для каждого кадра по формуле:

pos.x += separation * (pos.w - convergence)

Это очередной хак на уровне D3D9/10 API, и для его включения необходимо разместить fourcc 'NV3D' в последней строке staging текстуры формата RGBA32F, данные из которой впоследствии необходимо скопировать в обычную текстуру посредством UpdateSurface()/CopySubresourceRegion(). При копировании драйвер разместит данные для левого кадра в целевой текстуре, а для правого (как я понимаю) создаст shadow copy и разместит в ней данные для правого кадра, взяв их по определённому адресу (см. раздел 3D Video в GDC09 3DVision The In and Out). Если всё сделать в том порядке, который предлагается в примере, то шейдер действительно будет семплировать различные данные по одним и тем же текстурным координатам. Ну а дальше уже дело техники...

Одним из подводных камней, о который я споткнулся, является то, что недостаточно просто при старте скопировать данные в стереотекстуру и в дальнейшем пользоваться ею. Всё дело в том, что 3D Vision не включается с первым же кадром: драйвер сначала собирает некоторую информацию о первых кадрах, и только потом включается стереорежим. После его включения и нужно создавать стереотекстуру, а лучше всего проверять  переключение режимов функцией NvAPI_Stereo_IsActivated() из NvAPI, и обновлять её после изменения результата. А в примере StereoIssues идут ещё дальше: т. к. пользователь может динамически изменять такие параметры 3D Vision как EyeSeparation и Convergence, то необходимо отслеживать изменения этих параметров и своевременно обновлять данные в стереотекстуре.

четверг, 5 января 2012 г.

Игры

Я работаю в Ubisoft не потому, что мне нравится играть в игры, а просто потому, что мне нравится их программировать :)

Intel Celeron M

Из-за того, что в моём ноуте стоит сабжевый процессор, IE 8 на нём безбожно тормозит и сёрфинг по Сети лишь раздражает. Ситуация с Opera лишь чуть лучше. По законам копроэкономики я как потреблядь должен был побежать в магазин и прикупить что-нить с намного более мощным CPU (желательно с 4-мя ядрами!). Но вот незадача, Google Chrome на текущей конфигурации летает.

Не пишите говнософт.

понедельник, 2 января 2012 г.

ТВ

По случаю Нового года довелось посмотреть телевизор.

Я смотрю, его прямо оккупировали, каждый год одни и те же рожи! Все сытые и довольные, с улыбками поздравляют нас и как всегда желают быть счастливыми! Сколько, интересно, им это стоит и кому на это жаловаться?

P.S. Да ещё этот ваш карлик, карманный президент, выскочит в перерыве на экран и в двух словах расскажет, как у вас там всё пиздато, и что Россия катится полным ходом в светлое будущее.