Приклеено : Трансляция в ЖЖ

Данный блог автоматически транслируется в у меня в ЖЖ, для тех кому его удобнее читать там.

Игра над которой я работаю - анонсирована!

Ура!!
Наконец то можно говорить, что мы делаем.

https://www.youtube.com/watch?v=0_eV6RhtRr0

Запись в бету

http://sf.my.com/

Ура! Сайт официально запущен

Ура! сайт нашей игры наконец то запущен!

http://www.CaptainBloodGame.com/
Всем добро пожаловать!

Наша игра

Превью нашей игры на GameTrailers

Волшебный блендинг

Давненько ничего не писал в блог, буду исправляться потихоньку.

В системах частиц и всяких спецэффектах часто нужно рисовать с различным блендингом,
классически это add blend и alpha blend.

Обычно рисуют сортированные (по эмиттерам или по частицам) партиклы с alpha blend, а потом поверх кладут частицы с add blend (их уже можно не сортировать).

Но, художникам иногда очень хочется нарисовать партикл с add blend, а поверх него с alpha blend.

И уж точно программистам всегда хочется рисовать все частицы за один batch, независимо от их блендинга.

Итак, немного простой математики:
Формула блендинга destColor = sourcePixel * srcBlend + screenPixel * dstBlend;

Для add blend задают srcBlend и dstBlend как one, получая:

//blend function
destColor = sourcePixel + screenPixel;
_Winnie C++ Colorizer

Для alpha blend задают srcBlend как srcAlpha и dstBlend как invSrcAlpha, получая:

//blend function
destColor = sourcePixel * srcAlpha  + screenPixel * invSrcAlpha;
_Winnie C++ Colorizer

Так как же совместить эти два режима блендинга в одном ?

Задаем srcBlend как one и dstBlend как invSrcAlpha, получаем:

//blend function
destColor = sourcePixel + screenPixel * invSrcAlpha;
_Winnie C++ Colorizer

Теперь, следите за руками :) несколько манипуляций в pixel shader:

//0.0 это Add blend
//1.0 это Alpha blend
float addOrBlend = 0.0; 

float4 ps_test( PS_INPUT pix ) : COLOR0
{
   float4 texel = tex2D(baseMap, pix.texCoord0);
   texel.rgb = texel.rgb * texel.a;
   texel.a = texel.a * addOrBlend;
   return texel;
}
_Winnie C++ Colorizer

Посмотрим, что же получилось и самое главное, как.

Итак наша формула блендинга destColor = sourcePixel + screenPixel * invSrcAlpha;
при значении addOrBlend = 0.0; получаем (с учетом математики в pixel shader):

//blend function
destColor = sourcePixel * srcAlpha  + screenPixel;
_Winnie C++ Colorizer

при значении addOrBlend = 1.0; получаем (с учетом математики в pixel shader):

//blend function
destColor = sourcePixel * srcAlpha  + screenPixel * invSrcAlpha;
_Winnie C++ Colorizer

Как видно, add blend получается не совсем “чистый”, цвет текстуры умножается на альфу, но это дает нам возможность плавно менять addOrBlend соответственно плавно изменяя режим блендинга.

Если вдруг нужно только бинарное значение и хочется “чистый” блендинг, можно немного изменить наш шейдер: texel.rgb = texel.rgb * lerp (texel.a, 1.0, addOrBlend);

С помощью данной техники у нас в Приключениях капитана Блада сделаны партиклы.
Партикловый художник может графиком задавать блендинг у каждой частицы, при этом все частицы сортированные и рисуются за один batch.

Надеюсь это пригодится и в вашем проекте.

C++ reflection - возвращение

Как заметили на форуме gamedev.ru, прошлая версия DynamicCast проигрывала стандартной версии при использовании на Intel процессорах, с длинными именами классов (>20 символов) , на AMD же все по прежнему было хорошо, это меня удивило и я стал разбираться.

Профайлер подсказал, что все дело в использовании стандартного strcmp. После использования самописного аналога - Intel и AMD снова работают как и ожидалось. Как приятный бонус скорость DynamicCast подросла почти на 50% и на моем AMD 3000+ в 4.5 раза быстрее чем стандартный cast.

Так же в качестве бонуса, по подсказке my.name,  сделал автоматическое определение базового класса (без указания его в макросе объявляющим метаданные) и перенес определение рефлекшн аттрибутов в хидер.

В общем вот: новая версия reflectionLibrary_v2

Математика / Геометрия

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

Unreal Development Kit - лайтмапы

Забавно, что в недавно вышедшем UDK текстуры лайтмапов пожаты как я писал в этом посте : Alpha-test высокого качества, только вместо альфы используют текстуры L8. Реально можно сильно сэкономить на разрешении лайтмапов, жалко у нас их нет в движке.

C++ reflection с блэк-джеком и шлюхами

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

Первая проблема в том, что все функции оперирующие или выдающие как результат множества объектов должны работать с базовым типом, каким нибудь IEntity. Для примера: выбрали мы из мира с помощю AABB набор игровых объектов и хотим нанести урон, всем кто поддерживает интерфейс IDamageable, для этого обычно используют dynamic_cast или какой нибудь getType. На консолях, где RTTI в релизном билде обычно запрещен - приходится изобретать свои методы (см. библиотеку от Insomniac).

Вторая проблема, отсутсвие в С++ Property и средств для работы с ними. Движки по типу Unreal Engine3 и CryEngine приучают творческих людей работать с набором параметров у любого игрового объекта - будь то объект миссии, текстура или спецэффект, что совершенно логично и удобно.

Но вот незадача, в C++ отсутсвует возможноть получить какие либо метаданные класса - что бы узнать его поля / свойства и использовать это для управления объектом в реальном времени. Т.е. написание удобного и визуального средства редактирования C++ классов становится проблемой.

Существует множество вариантов решения, кто-то пишет уникальные редакторы для уникальных объектов, кто-то использует кодогенерацию мета-информации и классов, кто-то просто игнорирует проблему или пишет редактируемые объекты на скриптовых языках с поддержкой Reflection.

Третья проблема, система автоматического save / load любых объектов. Делают её обычно банально через virtual ::save(file * f) и virtual ::load (file *f) для всех объектов которым нужно уметь сохраняться, и с разной степенью успеха борются с проблемой при загрузке - как, имея имя класса в const char * создать экземпляр класса.

Чтобы решить эти проблемы один раз и навсегда, я написал маленькую библиотеку С++ reflection (скачать тут) которая позволяет удобно добавлять произвольную метаинформацию к любым классам, не требует специального препроцессора или кодогенерации и намного быстрее существующих средств в языке (dynamic_cast) или библиотек.

Вот пример объявления метаданных внутри cpp файла.

BEGIN_REFLECTION_METADATA(foo_class, base_class)
    ATTRIBUTE_FLOAT("myFloatProperty", &foo_class::setFloatFunc, &foo_class::getFloatFunc);
    ATTRIBUTE_INT("myIntProperty", &foo_class::setIntFunc, &foo_class::getIntFunc);
END_REFLECTION_METADATA(foo)
_Winnie C++ Colorizer

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

Пример использования dynamic_cast

base * base_ptr = new foo();
foo * foo_ptr = base_ptr->DynamicCast<foo>();
_Winnie C++ Colorizer

Плюсы и минусы библиотеки:
+ можно создавать классы по текстовому имени класса
+ получать список всех классов с метаинформацией и создавать их по метаинформации
+ получать список аттрибутов (property) для любого класса
+ быстрый dynamic_cast
+ получать метаинформацию из созданного экземпляра класса
+ аттрибуты поддерживают наследование
+ вся информация строится в compile time

- нужно писать небольшие макросы
- не поддерживается множественное наследование (для меня это фича)
- незначительно увеличивается время компиляции (нужно все таки хеши считать)

Вот несколько примеров скорости работы:

"Xenon X360 (3.2 GHz)"
-------------------
dynamic_cast 9.52M casts per second
reflection::cast 18.94M casts per second

прирост быстродействия x 1.99

"AMD 64X2 3800+ (2.0GHz)"
-------------------
dynamic_cast 12.02M casts per second
reflection::cast 35.50M casts per second

прирост быстродействия x 2.95

"Core2Duo E6750 (2.66GHz)"
-------------------
dynamic_cast 26.49M casts per second
reflection::cast 45.70M casts per second

прирост быстродействия x 1.73
_Winnie C++ Colorizer

Буду рад мнениям и результатам тестов - скомпилированный тест производительности лежит внутри архива. Повторяю ссылку, что бы не искать её выше по тексту. (Библиотека reflection)

UPDATE: Обновил архив, ускорил получение метаданных из указателя на объект.

Миллион батчей в секунду на Xbox360

Во время очередного витка оптимизации на Xbox360, захотелось очень дешевых по производительности батчей - т.к. батчей в кадре было достаточно много, а CPU времени они кушали еще больше, при тысяче батчей в кадре тратилось 20-25 ms на установку констант и вызов DP. CPU лимит в 40-50 FPS явно не устраивал.

Как потом выяснилось часть оверхеда была от использования D3D Effects в качестве шейдерной системы. Большое количество Load Hit Store и двойное копирование всех констант, были одной из проблем.

Итак, задача: 30 тысяч строк написанных и отлаженых шейдеров которые нельзя менять и желание иметь в runtime подобие PS3 libGCM и полностью контролировать генерацию command buffer.
Я начал рыть в сторону PrecompiledCommandBuffer т.к. там явно была возможность записать CommandBuffer и потом отправить его в GPU. К сожалению, изменять параметры у записанного CB можно очень ограниченно, поэтому данный метод не подошел.

В результате экспериментов я получил дамп памяти GPU буфера и стал его разбирать ручками. К счастью, Xenos это развитие чипа ATI R500, а на R500 документация открыта (спасибо AMD/ATI). Очень быстро был готов парсер CB, который выдавал полную информацию, о том что лежит в буфере.

Выяснилось, что часть операций которые можно делать из D3D совсем по другому мапяться в железо, да и Runtime D3D сохраняет зеркальные стейты GPU, что тоже не бесплатно. Также паралельно выяснилось, что размер CB на каждом батче слишком большой, т.к. частично шейдеры эмбедились в CB и устанавливалось очень много констант (они всегда эмбедятся в буфер).

Как результат всех эксперементов я написал компилятор на входе берущий D3D Effect файл и генерирующий из него кусочки CB (кусочки что бы можно было не сабмитить уже установленные в GPU данные), так же runtime ремапинг адресов, сабмишн данных в GPU и всякий дополнительный код.

Теперь рендер батча стал просто заполнением буфера памяти, поборовшись Load-Hit-Store на заполнении памяти и сделав кеширование CB в системной памяти (общая память CPU и GPU на X360 и на PC в режиме WriteCombined, что отключает все кеши и ведет к разным пенальти из за непоследовательной записи) я получил хороший прирост производительности.

После всех основных оптимизаций, батчи стали стоить до 4ms на кадр (вместо 25ms), профайлинг показывал узкое место на доступе к памяти (констант было от 200 до 400 на батч). У ATI GPU есть очень приятная фича, на вход микрокода вершинного шейдера приходит номер индекса, и сборка вершины происходит в шейдере с возможностью random read из потока вершин. Я переделал большинство констант на vertex stream (данные vertex stream заполнялись в другом cpu потоке) и избавился от 90% констант при отрисовке, результат: 1-2ms из секунды это расходы на отрисовку 1000 батчей, на этом и остановился.

В теории успех можно было развить дальше, т.к. можно вести отрисовку с нескольких потоков (при рендере мы просто работаем с памятью, не трогая D3D вообще, D3D нужен только для сабмишена данных в GPU) - но на текущем проекте этого не потребовалось. CPU лимит в миллион батчей в секунду кажется мне достаточным, да и PC такого не переживет - надо все таки и о второй платформе помнить.

Итого:

Было: D3D Effects + D3D runtime
0.02ms batch * 1000 = 20ms на кадр (50000 batches per second)

Стало: Своя библиотека работы с GPU:
0.001ms batch * 1000 = 1ms на кадр (1000000 batches per second)

←Older