Записки о геймдеве

Unreal Development Kit - лайтмапы
[info]joker_ru

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


Данный пост размещен в моем личном блоге SergeyMakeev.com.

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

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

Первая проблема в том, что все функции оперирующие или выдающие как результат множества объектов должны работать с базовым типом, каким нибудь IEtity. Для примера: выбрали мы из мира с помощю 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: Обновил архив, ускорил получение метаданных из указателя на объект.


Данный пост размещен в моем личном блоге SergeyMakeev.com.

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

Во время очередного витка оптимизации на 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)


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Много бесплатных текстур
[info]joker_ru

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


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Alpha-test высокого качества
[info]joker_ru

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;


Данный пост размещен в моем личном блоге SergeyMakeev.com.

GDC09 free content
[info]joker_ru

Тут лежит free content с GDC07, GDC08, GDC09 - в принципе не очень много, но есть всякие интересные доклады.

Вот тут, еще немножко есть http://msinilo.pl/blog/?p=345

И вот тут, еще про God of War 3


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Полезный ключ cl.exe /EP
[info]joker_ru

Раньше, когда я занимался в свободное время 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 компилятору.

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


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Нормалмапы, как они есть
[info]joker_ru

Все давно знают и используют у себя в движках нормал мапы. Художники делают хайрез, “снимают” карту нормалей и все при этом счастливы. Нормалмапы естественно в tangent space, что бы все как у людей было и про мэпинг артистам не думать. Но иногда, на модельках с нормалмапом появляются “швы” их как-то обычно забарывают, но особо про это никто не думает и даже на ушедших в печать играх часто можно увидеть швы на нормалмапе.

Так вот, если подумать, почему же появляются ошибки ? Для этого представим как все работает: запекалка нормал-мапа с хайреза, трассирует лучи и строит карту нормалей (из карты разницы высот или другим способом - неважно) но полученная карта нормалей получается в object space. Затем, запекалка нормал-мапа сама СЧИТАЕТ tangent space и используя посчитанные tangent матрицы, конвертирует полученные нормали в tangent пространство.
В этом и кроется подвох: если расчет tangent матриц у вас в игре и в запекалке карт нормалей отличается (а он 99% будет отличаться, т.к. в каких случаях разрывать мэпинг а в каких нет, даже в разных 3D пакетах реализованно по разному) в местах несовпадения tanget векторов будет шов. Обычно для устранения эффекта стараются не делать зеркальный мэпинг для моделек с нормал-мапом, это минимизирует ошибку, но не полностью её исключает.

Возникает логичный вопрос, что же делать ? Можно конечно пытаться “угадать”, как считается tangent пространство в модуле запекающем нормали в 3D пакете, но это не самый лучший выход на мой взгляд. К счастью недавно я обнаружил вот эту програмку, она умеет считать нормал-мапы используя CUDA и прочие радости и САМОЕ ГЛАВНОЕ позволяет написать свой генератор tangent матриц, такой же как у вас в игре. После этого про швы можно больше не думать. Надеюсь кому-нибудь это поможет уменьшить количество проблем в проекте. К сожалению alien интерфейс делает не очень казуальной использование софтинки, но привыкнуть можно.


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Плавный deltaTime и GPU : part 2
[info]joker_ru

Недавно, я написал как избежать рванного deltaTime используя простую синхронизацию CPU и GPU.

Семен справедливо заметил, что такой способ синхронизации приведет к тому, что некоторое время в начале кадра GPU будет проставивать в ожидании данных. И, что нужно делать несколько Query синхронизируясь на кадр назад. Тогда и deltaTime будет плавный и GPU idle пропадет.

Вот ссылка на код который синхронизирует на любое заданное количество кадров через серию query. По тестам наибольший выигрыш получился на мощных видеокартах (до 50% от предыдущей схемы), на слабых видеокартах эффект заметен гораздо меньше, но тоже присутствует.


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Факинг, вирусы
[info]joker_ru

Вот, народ сегодня начал писать про вирус новый, я между тем с ним уже два дня веду войну на работе. При том, что обнаружил вирус чисто случайно.

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

Вот тут про него умные люди пишут, я же от себя добавлю по результатам борьбы с ним: никакими из существующих антивирусов по состоянию на 13 января его обнаружить не удалось, никакие ad-aware проги его не видят. Между тем вирус внедряясь в svhost пробрасывает весь мой трафик через огромную сеть relay серверов в Амстердаме, совершенно прозрачно для меня и устанавливает руткит на зараженный компьютер, в общем вся ваша информация сразу перестает быть конфиденциальной.

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

Определяется с последними апдейтами как: Worm:W32/Downadup.AL, Net-Worm.Win32.Kido, Worm:W32/Downadup.AL, Worm:Win32/Conficker (Microsoft), W32/Conficker.worm.gen (Symantec), Mal/Conficker (Sophos), будте осторожны!

p.s. из антивирусов стоял корпоративный Nod32 со свежими апдейтами :(


Данный пост размещен в моем личном блоге SergeyMakeev.com.

База кода в проекте
[info]joker_ru

Простая и удобная утилита которая считает метрики кода. Натравил на нашу прелесть и вот, что получилось:

Игра + Движок
——————————————
1284 файлов
320735 строк кода
65635 пустых строк
39803 строк с комментариями

Редакторы (уровней, спецэффектов, графов-анимации, звуков, ForceFeedBack эффектов)
——————————————
383 файлов
72286 строк кода
14476 пустых строк
8790 строк с комментариями

Шейдеры:
——————————————
101 файл
12131 строк кода
2571 пустых строк
2609 строк с комментариями

Плагины и тулзы для арта:
——————————————
113 файлов
27846 строк кода
5242 пустых строк
4850 строк с комментариями

Все это пишут и поддерживают 5 программистов (4 и лид) + Я (30-40% времени трачу на код и архитектуру, больше не получается)

Естественно в метриках нет никаких SDK и прочего шлака, все это мы написали своими руками.

p.s. ради интереса нашел в TXT “Войну и Мир : Том 1″ и померил. Получилось: 12821 строка, понятно, что строчки там длинные, но все равно круто.


Данный пост размещен в моем личном блоге SergeyMakeev.com.

NVidia и веселье
[info]joker_ru

Вот кстати вспомнил: в свое время пришлось с этим помучаться, на некоторых драйверах Nvidia, pow(n, 0.0) возвращает -1 Если кто использует степень спекуляра которую художники могут менять, то на некоторых драйверах будут неприятные артефакты.


Данный пост размещен в моем личном блоге SergeyMakeev.com.

WideCharToMultiByte
[info]joker_ru

К сожалению в нашем проекте, внутри еще живет ANSI для печати текста, а локкит мы естественно сделали unicode (это обычный unicode csv, легко парсить и локализаторам удобно)

Через некоторое время выяснилось, что WideCharToMultiByte на Xbox360, который в соответствии с текущей локалью конвертирует UTF16 в ANSI не работает, для русского языка! Т.к. просто тупо нет русской локали, а при выборе русского языка в Dashboard, возвращается локаль UNITED_STATES. В результате пришлось писать самому руками конвертацию для ANSI 1251 и 1252. Так вот для тех, кто будет писать конвертацию, следующие ссылки (я о них узнал, когда уже все сделал сам - в общем как обычно изобретал велосипед)

Таблицы соответствия юникода и ANSI для различных code pages лежат вот тут.

А вот тут лежит либа, которая все что угодно может делать с кодировками, к сожалению либа “заражена” GPL лицензией поэтому она только на посмотреть, хотя вроде и LGPL есть при распространении как *.lib *.h


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Плавный deltaTime и GPU
[info]joker_ru

PC разработчики почему то, часто забывают или не интересуются как работает DirectX изнутри. Типичный пример: большинство думает, что Present блокирующий вызов и приводит к синхронизации. На самом же деле Present фактически просто добавляет в PushBuffer команду которая делает flip для отображаемой на экране GPU памяти, но это в том случае, если все хорошо.

Если же CPU заполняет PushBuffer, быстрее чем GPU успевает его исполнять, DX Runtime некоторое время (2 кадра) продолжает класть все команды в PushBuffer надеясь, что GPU успеет или CPU будет поменьше данных давать.

После того, как GPU “отстал” от CPU на пару кадров происходит ожидание GPU при этом CPU простаивает (stall frame). Это очень неприятно, т.к. deltaTime становится крайне неравномерным и брюква становится уже не той (см.лекцию: Баткин Борис. О некоторых особенностях приготовления брюквы.)

В случае проблем график deltaTime типично принимает вид, но может и отличаться (90% случаев на практике, график был именно такой)

Возникает вопрос: как же это забороть ? Забороть совсем нельзя (зоопарк конфигураций, CPU с GPU могут отличаться на порядки по загруженности/производительности), можно уменьшить вред, от этого эффекта, для этого нужно сделать выхов Present синхронизирующим. Что бы получить следующую картину:

При использовании DX9 можно использовать механизм Query, в предыдущих DX Query отсутствуют, но можно использовать Lock/Unlock бэфбуффера.

Код для DirectX9:

...Init...

D3D()->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);

...Frame...

HRESULT issueResult = &pEventQuery->Issue(D3DISSUE_END);
if (issueResult != D3DERR_DEVICELOST)
{
 HRESULT queryResult = &pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH );
 if (queryResult == S_FALSE)
 {
   for (;;)
   {
     queryResult = &pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH);
     if (queryResult != S_FALSE)
     {
       break;
     }

    //Тут делаем, что хотим - видеокарта не успевает,
    //CPU время можно тратить
    Sleep(0);
   }
 }
}

D3D()->Present();
_Winnie C++ Colorizer

Код для DirectX8:

D3DLOCKED_RECT lockedRect;
d3d8BackBufferSurface->LockRect(&lockedRect, NULL, D3DLOCK_READONLY );
d3d8BackBufferSurface->UnlockRect();

D3D()->Present();
_Winnie C++ Colorizer

Код для DirectX7:

DDSURFACEDESC2 surfaceDesc;
sDesc.dwSize=sizeof(surfaceDesc);
d3d7BackBuffer->Lock(NULL, &surfaceDesc, DDLOCK_WAIT, NULL);
d3d7BackBuffer->Unlock(NULL);

D3D()->Present();
_Winnie C++ Colorizer

Данный пост размещен в моем личном блоге SergeyMakeev.com.

про DirectX
[info]joker_ru

Меня периодически спрашивают программисты из различных геймдев компаний всякое про DirectX. Ну почему какие то вызовы DX иногда тормозят, а иногда нет. Что происходит в Present и т.п. (на Xbox ближе к железу и проще ответить что оно там внутри драйвера/DX делает ) Вот нашел неплохой DXFAQ который на 90% этих вопросов отвечает.

Вот еще добавлю от себя. Не все знают, что на некотором железе tex2Dproj подменяется на tex2D и деление. А texCUBE к примеру даже принципиально не может фильтровать пиксели на эджах кубмапа, т.к. все равно сводится все внутри к вызову обычного tex2D (см. сюда глава Mapping Texture Coordinates to Cube Map Faces или сюда страница 5)


Данный пост размещен в моем личном блоге SergeyMakeev.com.

Волшебное затухание
[info]joker_ru

В игродеве частенько бывает нужно, что нибудь плавно растворить или сфейдить. Тот-же звук к примеру или объекты какие нибудь по альфе, обычно не задумываясь пишут getDistance() до объекта и потом считают коэфицент фейдинга, но getDistance() не менее класически содержит в себе sqrt, а если объектов много ?

Читать дальше )
Данный пост размещен в моем личном блоге SergeyMakeev.com.

Отладочные символы для windows
[info]joker_ru

Оказывается, если скачать Debugging Tools for Windows вот здесь, а потом выполнить в консоли: symchk /r c:\windows\system32 /s SRV*c:\symbols\*http://msdl.microsoft.com/download/symbols то тулза автоматически просканирует всю виндовую директорию и заберет нужные PDB. И можно в отладчике вместо ntdll.dll и компании в колстеке видеть человеческую инфу


Данный пост размещен в моем личном блоге SergeyMakeev.com.

(no subject)
[info]joker_ru
Программерское, HDR упаковка в ARGB8 )
Tags:

Скорость печати
[info]joker_ru
Му-ха-ха !! 283 осмысленных знака в минуту ! Почти 4.71 в секунду



LinkedIn
[info]joker_ru
View Sergey Makeev's profile on LinkedIn

Home