Приклеено : Трансляция в ЖЖ
Данный блог автоматически транслируется в у меня в ЖЖ, для тех кому его удобнее читать там.
Данный блог автоматически транслируется в у меня в ЖЖ, для тех кому его удобнее читать там.
Как заметили на форуме gamedev.ru, прошлая версия DynamicCast проигрывала стандартной версии при использовании на Intel процессорах, с длинными именами классов (>20 символов) , на AMD же все по прежнему было хорошо, это меня удивило и я стал разбираться.
Профайлер подсказал, что все дело в использовании стандартного strcmp. После использования самописного аналога - Intel и AMD снова работают как и ожидалось. Как приятный бонус скорость DynamicCast подросла почти на 50% и на моем AMD 3000+ в 4.5 раза быстрее чем стандартный cast.
Так же в качестве бонуса, по подсказке my.name, сделал автоматическое определение базового класса (без указания его в макросе объявляющим метаданные) и перенес определение рефлекшн аттрибутов в хидер.
В общем вот: новая версия reflectionLibrary_v2
Очень неплохая подборка математики, необходимой геймдев программисту. Особенно удобно использовать как справочник, все в одном месте и на понятном языке ![]()
Забавно, что в недавно вышедшем UDK текстуры лайтмапов пожаты как я писал в этом посте : Alpha-test высокого качества, только вместо альфы используют текстуры L8. Реально можно сильно сэкономить на разрешении лайтмапов, жалко у нас их нет в движке.
Отсутствие 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, захотелось очень дешевых по производительности батчей - т.к. батчей в кадре было достаточно много, а 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)
Сегодня чуть не потерял данную ссылку, на замечательный сайт с огромным количеством бесплатных текстур для игр. Оставляю ссылку в посте, что бы не забывать. А кому еще пригодится, мне не жалко. Текстуры можно использовать в коммерческих продуктах, плюс удобный каталог - что еще нужно для счастья.
Valve уже давно публиковали свой доклад SIGGRAPH 2007 “Improved Alpha-Tested Magnification for Vector Textures and Special Effects” о том, как очень получить сверх-качественные текстуры с альфа каналом, очень низкого разрешения.
Техника очень проста: берем альфа канал текстуры большого разрешения, к примеру 4096×4096 и уменьшаем до размера 64×64 или меньше со специальным фильтром.
Фильтр в результирующую альфу записывает дистанцию до первого непрозрачного пикселя на большой текстуре, вместо обычной альфа-маски. Дистанцию уже можно корректно интерполировать LINEAR фильтром, в отличие от альфа маски - а обычный ALPHA_REF задает задает границу дистанции после которой пиксель считается прозрачным.
В результате: получаетсся практически векторное качество альфа-маски текстуры и работает на любом GPU поддерживающем LINEAR фильтрацию и альфа-тест, что снова актуально учитывая всякие NintendoDS и iPhone.
Я накидал по данному докладу тестовый фильтр (скачать тут), результаты более чем хороши, попробуйте сами.
Пользоваться так:
DistanceToAlphaFilter исходная_текстура текстура_результат.tga resolution_x resolution_y
Для загрузки файлов используется D3DXCreateTextureFromFile, поэтому нужен файл D3DX9_39.dll (в комплекте). Форматы исходной текстуры могут быть: bmp, dds, dib, hdr, jpg, pfm, png, ppm, tga
Для отрисовки получившейся маски нужно установить следующие стейты:
ALPHABLEND = false;
ALPHATEST = true;
COLORARG1=texture;
ALPHAARG1=texture;
Тут лежит free content с GDC07, GDC08, GDC09 - в принципе не очень много, но есть всякие интересные доклады.
Вот тут, еще немножко есть http://msinilo.pl/blog/?p=345
И вот тут, еще про God of War 3
Раньше, когда я занимался в свободное время J2ME играми, очень напрягало отсутствие препроцессора в java, т.к. сложно было делать и поддерживать много микроизменений в коде требуемых издателями/особенностями телефонов и т.д. Тогда я сделал свой препроцессор для java с синтаксисом как у C++, недавно выяснилось - снова изобретал велосипед, но т.к. на момент написания я информации не нашел, напишу об этом тут:
У cl.exe есть отличный ключик /EP о котором естественно написано в MSDN. Так вот этот ключик обрабатывает препроцессром заданый файл и результат выдает в stdout. Обрабатывается файл полноценно, гораздо лучше чем мой велосипед, на который я еще и время зря тратил.
Файл: test.java
//test.java
//------------------------------------------------------------------
import java.io.*;
import javax.microedition.io.StreamConnection;
import javax.microedition.lcdui.*;
import javax.microedition.rms.*;
#ifdef NOKIA
import com.nokia.mid.ui.FullCanvas;
#endif
public class Game
#ifdef NOKIA
extends FullCanvas
#else
extends Canvas
#endif
implements Runnable
{
public Game()
{
#ifdef SOUND_SUPPORT
#include "sound_check.java"
#endif
}
}
//------------------------------------------------------------------
|
| _Winnie C++ Colorizer |
Файл: sound_check.java
//sound_check.java
//------------------------------------------------------------------
//check if sound is supported
Class c = null;
try
{
c = Class.forName("javax.microedition.media.Player");
} catch (Exception ex) { }
if (c != null)
{
System.out.println("Sound supported");
return true;
}
System.out.println("Sound NOT supported");
System.out.println(__DATE__);
//------------------------------------------------------------------
|
| _Winnie C++ Colorizer |
И запустим на них: cl /C /I”c:\commonfiles\” /DNOKIA=1 /DSOUND_SUPPORT=1 /EP /Tctest.java > output.java в результате получим output.java который уже можно скармливать java компилятору.
Так же можно делать с любым текстом где нужен препроцессор, всякие скриптовые языки/генерация кода и т.д., очень полезно знать, что такая фича есть.