Pull to refresh
10
0
Yuriy O'Donnell @Kayru

User

Send message
К сожалению, anonymous namespace не помогает с коллизиями символов.
У unity билдов есть еще одно достоинство — у компилятора появляется больше возможностей для оптимизации. Теоретически, компилятор сможет практически всё что делает линкер с link time code generation.
Еще один прием — вместо дополнительных конфигураций (и головной боли от отключения всех новых *.cpp файлов при добавлении в проект), можно просто создать дополнительный _проект_ с одним единственным cpp файлом. И еще, можно вытащить все общие параметры проектов в отдельные property sheets. Они работают просто «из коробки» для PC и 360 проектов. PS3 VSI представляет собой небольшую головную боль, но и его тоже можно настроить (VSI может использовать user macros, которые заданы в property sheet).
На мой взгляд, у unity есть всего три недостатка (один из которых описан в статье):
1. Статические, инлайн и anonymous namespace функции, переменные и типы должны иметь уникальные имена. Автор статьи призывает ими не злоупотреблять, но лично я с этим не согласен. Эти механизмы очень хороши для того чтобы прятать внутренности систем от пользователей публичных интерфейсов. Конечно, есть множество альтернатив для достижения того-же эффекта, но лично мне anonymous namespace-ы кажутся элегантным решением.
2. Когда один из cpp файлов include-ит какой-то заголовок, он становится доступным для всех последующих cpp файлов. Таким образом unity проект может компилироваться без проблем, но не-unity может выдавать ошибку о том, что некоторые символы не определены, т.к. необходимый заголовок на самом деле не включен. Это довольно серьезная проблема когда не-unity конфиг собирается долго и разработчики коммитят только тестируя в unity. На мой взгляд, самое простое решение этой проблемы — только поддерживать unity! У нас в проекте, например, несколько unity файлов (т.к. они могут компилироваться параллельно с помощью -j, /MP, SN-DBS или IncrediBuild).
3. При работе с одним отдельным cpp файлом, собирается весь unity файл. Это частично решается созданием нескольких unity файлов (как у нас в проекте) и созданием одного специального пустого unity файла. При необходимости в быстрой итерации, можно убрать отдельные файлы из «главного» файла и включить в специальный. Кроме того, это можно сделать по #ifdef YOUR_NAME для группы файлов и смело коммитить в депо.
Я думаю что процент людей которые будут это делать достаточно мал и вполне компенсируется тем, что 360 теперь в гораздо более выгодном положении с точки зрения паблишеров. Разработка для PS3 и раньше была гораздо дороже и рискованей чем для 360. А теперь тем более.
Сейчас скиннинг вполне адекватный. Пожалуй, самый распространенный вариант — multi-weighted c 3-4 костями на вершину. Для лицевой анимиции могут использоваться morph targets. На современном железе можно сделать практически всё что угодно.

Скелетные анимации смешиваются на лету для реалистичных переходов (например шаг->бег). Additive анимации накладываются поверх. Плюс IK для «foot planting». Плюс физика для особых случаев (одежда, рагдолл и прочее).
Плагин к DCC (source->export) всего один. А пайплайнов export->native может быть много, т.к. различия между платформами очень существенные. Начиная c порядка байт (x86 PC — little endian, консоли — big endian) и заканчивая оптимизацией вертексных данных мешей для конкретного железа.
Обычно игровые данные живут как минимум в 3х видах:

1. «Source» — формат в котором работают художники (.max, .psd, etc)

2. «Export» — промежуточный формат, с которым удобно работать программистам (collada, fbx или вобще свой формат и свои плагины к DCC пакетам). Скорость здесь на сильно важна, поэтому даже текстовый формат подойдет. Если надо, performance-critical части могут кешироваться в более оптимальном виде.

3. «Native» — бинарный формат, который загружает игра. Здесь данные хранятся так, как удобно конкретной платформе. Т.е. разные форматы для PC, 360, PS3, Wii и прочих.
В современных процессорах еще есть такая штука как pipelining и register renaming. Благодаря этому, процессор может одновременно выполнять несколько операций если между ними нет зависимостей. Это, опять-же, помогает спрятать доступ к памяти.
Сами инструкции выполняются примерно с одинаковой скоростью (обычно 1 инструкция за цикл). Но благодаря глубокому пайплайнингу, хороший код будет выполняться со скоростью ~2-3 инструкции/цикл.
1. Разница в 4 раза это еще хорошо. Здесь работает автоматический префетчинг. На PC, современные процессоры умеют предсказывать какие кэш линии будут скоро необходимы. Это работает даже при прыжке в несколько кэш линий. Необходимо только чтобы вы шли в одном направлении по памяти, с одинаковым шагом. Можно еще самому поставить hint-ы для префетчинга, но здесь необходимо очень аккуратно проверять результаты, скажем, VTune-ом. Попробуйте создать массив индексов для выших 100мб данных, отсортировать его случайным образом и потом опять сложить данные, используя случайные переходы.

Про SIMD — современные компиляторы умеют самостоятельно векторизировать ваш скалярный код. С тем или иным успехом. Intel C++ compiler умеет это делать лучше чем MSVC. Никакого ассемблера не ребуется.

2. В большинстве практических случаев это именно так, как я сказал.

3. Можно. Разные процессоры умеют по разному предсказывать где произойдет cache miss. Но инструкции они выполняют более-менее одинаково.
Cache-efficient код будет работать на порядок быстрее на любом современном процессоре.
1. Основная проблемма в современных процессорах — медленный доступ к основной памяти (L2 cache miss* стоит сотни циклов). Всевозможные приемы, как out-of-order execution, branch prediction и т.п. предназначены в первую очередь именно для того чтобы угадать где в будущем будет этот-самый miss и начать копировать память в cache. Современные компиляторы не способны особо помочь в этой ситуации (кроме как на совсем микро-уровне scheduling-а инструкций). Негативный эффект от L2 промаха может замедлить самый алгоритмически оптимальный, векторизированый (simd) код в 10 раз! Никакой апгрейд компилятора не поможет если вы прыгаете по памяти.
Так что ответ — ДА, реорганизация данных для оптимального использования L2 будет выиграшем.

2. Обычно такие трансформации *упрощают* код, т.к. отдается предпочтение более простым структурам (или нескольким массивам из простых структур). Функции, обрабатывающие эти структуры так-же становятся более простыми.

3. Нет, не будет. Будет только быстрее. В обозримом будущем память будет всё более дорогим ресурсом, т.к. будет всё больше ядер которые борятся за одновременный доступ.

Авторы статей — широко известные (в узких кругах геймдева) программисты. Они знают о чем говорят.

* для справки: кэш процессора организован в несколько уровней (обычно L1, L2 и L3). Каждый уровень разбит на мелкие участки — cache lines (обычно в 64 байта). В каждом участке хранится копия данных из основной памяти. Кроме кэша, у процессора есть регисры. Для маппинга физических адресов основной памяти используется совсем-совсем простая hash-функция (обычно бит-маска и сдвиг). Когда все вычисления происходят с регистрами — мы получаем оптимальную скорость. В этом случае компилятор это наш лучший друг и его качество сильно влияет здесь на производительность. Когда нужно записать новую информацию в регистр, процессор идет в L1. Если там есть данные — cache hit, всё хорошо и быстро (несколько циклов на i7). Если данных нет — cache miss, процессор идет в L2. Если нужные данные там есть (cache hit) — всё не плохо (десяток-другой циклов). А если данных нет — cache line запрашивается из основной памяти (. Это уже стоит сотни циклов.
Cache line это минимальное количество памяти, которую процессор может запросить. Поэтому если даже вы трогаете всего один байт в вашей структуре, из памяти будет передано 64. Кроме того, если суммарный размер вашей структуры, скажем, 65 байт — будет передано 128. И это еще не всё! Если у вас есть динамический массив из данных в которых каждый элемент 64б — у вас всё равно есть шанс передавать две кэш-линии при доступе к одному элементу. Это происходит изза выравнивания памяти.

Я мог бы продолжать еще очень долго. Возможно это была бы хорошая тема для отдельного хабратопика?
Qt и glib это довольно высоко-уровневые абстракции, а не стандартные кросс-платформенные решения.
В OGRE таймер устроен точно так-же как автор описал.
В юниксах обычно используют gettimeofday().
Точность WaitForSingleObject гораздо хуже QPC.
Ничего страшного не произойдет. Автор гарантирует исполнение QueryPerformanceCounter на одном ядре:
SetThreadAffinityMask(GetCurrentThread(), 0);
Надежных и точных (high resolution) кросс-платформенных решений НЕТ.
В геймдеве обычно используют оптимальное решение для каждой отдельной платформы (PC, PS3, 360, и т.д.), скрытое за общим интерфейсом.
Хорошо запомнилось:
— Про то, как в directx нет различия между pixel и vertex шейдерами.
— Про то как шейдеры не снижают производительность.
Посмотрел про OpenGL.

Хочу сказать что докладчик очень плохо понимает тему и в нескольких местах дает ложную информацию.

Рекомендую вместо просмотра почитать документацию:
developer.apple.com/iphone/library/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/Introduction/Introduction.html

И код практического применения:
code.google.com/p/cocos2d-iphone/
V^(1/gamma), конечно-же, должно быть V^gamma
1/gamma должно быть напряжение чтобы получить линейную яркость
1

Information

Rating
Does not participate
Location
Великобритания
Date of birth
Registered
Activity