Стриминг и эффективное чтение с DVD диска

 Данная лекция была подготовлена для конференции разработчиков компьютерных игр КРИ-2008.

Статья опубликована на сайте  DTF.ru

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

Рисунок 1. Загрузка.

Для избавления от экранов загрузки применяется стриминг – загрузка ресурсов на фоне. Статья рассматривает преимущества и проблемы этой технологии, стратегии загрузки, реализацию в современных «движках», а также вопросы эффективного стриминга с DVD. Несмотря на широкое использование стриминга в современных играх, в интернете практически отсутствует информация по этой теме, что и побудило меня к написанию этой статьи. Я постараюсь если не раскрыть, то хотя бы пролить свет на существующие проблемы и их решения на основе собственного опыта реализации этой технологии.

Что такое стриминг?

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

Рисунок 2. Мир Xenus 2: Белое золото [8].

Любой человек, который когда-либо играл в компьютерные игры, знает, как раздражают экраны загрузки («на самом интересном месте»). Хотя PC игроки, в принципе, уже с ними смирились, и считают их «неизбежным злом», неискушенные владельцы игровых консолей хотят вставить диск в DVD привод и играть без перерыва. Многие консольные игры, использующие cтриминг, не только не имеют экранов загрузки, но и сокращают время начального запуска, загружая контент во время показа вступительных роликов или показа меню.

Конечно, разработчикам хотелось бы иметь неограниченное количество оперативной памяти и быстрое устройство хранения, но в реальности игровые консоли есть такие, как они есть: относительно малый объём памяти, медленный DVD/Blueray привод. Именно с этими техническими ограничениями и призван бороться стриминг.

Ресурсы

Многие виды ресурсов занимают приемлемый объём памяти, и поэтому игры загружают их при старте и хранят в памяти все время. Это могут быть строки пользовательского интерфейса, скрипты, описание игровых объектов, описание материалов, шейдеры, списки текстур и другие мелочи, ради экономии памяти на которых не имеет смысла усложнять «движок».

Рисунок 3. Ресурсы Xenus 2: Белое золото.

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

Игра также может подгружать: звуки, музыку, модели, уровни, анимацию, collision mesh, скрипты, видео и т.д. В своей практике я никогда не встречал стриминг кода, хотя и это возможно:)

Что включает в себя поддержка стриминга

Итак, после прочтения вступления, Вы преисполнились уверенностью, что Вам обязательно нужно добавить поддержку стриминга в Вашу игру 🙂 Далее рассмотрим, что для этого необходимо доделать:
– возможность загружать ресурсы на фоне;
– ориентацию «движка» на асинхронную работу с ресурсами;
– стратегию предварительной загрузки;
– стратегию выгрузки ресурсов при недостатке памяти;

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

Если невозможно применять алгоритм с ожиданием загрузки, то игра должна обеспечить, чтобы к моменту использования ресурс обязательно был в памяти (стратегия предварительной загрузки). Например, при приближении игрока к некоторой зоне, абсолютно все ресурсы зоны загружаются в память.

Если игра стримит разные виды ресурсов, придется придумать, как выгружать их из памяти (стратегия выгрузки). Например, нам понадобилась текстура. Что лучше выгрузить? Другую текстуру, которая давно не использовалась, старый звук, или старую модель?

Классический (линейный) стриминг

Простейший вид стриминга можно показать на примере гоночного симулятора. Трасса разделена на участки примерно 200 метров. Дальность отображения – 150 м. Игра должна держать в памяти максимум две зоны, так как игрок может находиться на стыке зон. Следующая по дороге зона загружается на фоне. Зона, находящаяся позади, выгружается, когда новая зона подключается к игре.

Рисунок 4. Трасса гоночного симулятора – идеальный «мир» для линейного стриминга.

Благодаря линейности движения игрока, точно известен порядок загрузки зон. В RPG, где игрок может идти куда угодно, ситуация усложняется, но по-прежнему используется тот же принцип.

Весь мир разделяется на зоны компактной формы, часто – квадраты. Например, на рисунке изображена политическая карта региона, но она отлично иллюстрирует, каким может быть разделение.

 

Рисунок 5. Политическая карта. Примерно так можно разделять открытый игровой мир на зоны.

В данный момент в памяти находятся зоны, которые находятся внутри окружности, радиус которой равен примерно 150% дальности видимости; иначе зона выгружается. Фоновая загрузка новой зоны начинается, когда зона входит в окружность, радиус которой равен примерно 140% радиуса видимости. Величина радиуса окружности определяется скоростью загрузки зон и скоростью движения игрока. Цель такой схемы – обеспечить, чтобы в памяти всегда были зоны, которые видны игроку. Поскольку направление движения игрока ничем не ограничено, могут возникать ситуации, когда на момент окончания загрузки, зона оказывается не нужна, поскольку игрок прошел мимо, или пошел в обратную сторону.

В качестве примера можно привести такие игры: GTAIII (зоны – архитектурные элементы: дома, участки дорог, мосты, фонари, столбы, автобусные остановки и т.д.), Oblivion (квадраты), Gothic (квадраты), Operation “Flashpoint” (квадраты 1×1 км), Xenus (квадраты 200×200м).

Уникальные и shared ресурсы

Каждая зона использует определённый набор ресурсов разных типов. Набор данных зоны можно условно разделить на уникальные ресурсы зоны (например, ландшафт зоны, геометрию построек) и общие ресурсы (общие-shared), которые также используются и в других зонах (например – библиотека текстур, библиотека моделей). Существует несколько подходов обработки общих ресурсов.

Стратегия 1. Все ресурсы одной зоны считаются уникальными.

Рисунок 6. Все ресурсы зоны считаются уникальными. Это позволяет хранить все ресурсы зоны одним линейным блоком.

Преимущества:
– упрощение дизайна “движка”;
– зона – линейный блок на диске (хорошо для быстрого чтения с DVD и борьбы с фрагментацией памяти);
– однозначность, возможность поставить четкие требования к контенту;
Недостатки:
– приводит к дублированию одинаковых ресурсов в памяти и на DVD – увеличивается размер зоны в памяти, увеличивается размер игры на диске.

Стратегия 2. Некоторые типы ресурсов – общие (shared). Для каждого такого типа существует отдельный кэш. При загрузке зоны в кэш догружаются ресурсы, используемые этой зоной. При выгрузке зоны, из кэша выгружаются ресурсы, refcount которых равен нулю.

Рисунок 7. Некоторые ресурсы зоны – общие. Зона ссылается на элементы в кэше общих ресурсов.

Преимущества:
– позволяет сэкономить память;
– уменьшается объем загружаемых данных зоны (некоторые общие ресурсы уже могут быть в памяти);
Недостатки:
– фрагментация памяти;
– недетерминированный порядок чтения (много DVD Seek);

Стратегия 3. Некоторые ресурсы – общие (shared). Независимый кэш общих ресурсов. Ресурсы в кэш загружаются не в момент загрузки зоны, а по надобности.

Рисунок 8. Независимый кэш ресурсов. У зоны нет прямых ссылок на элементы кэша. «движок» находит элемент в кэше в момент рендеринга.

Зоны могут быть достаточно большими. Когда игрок будет пересекать зону, то некоторые текстуры зоны не будут использованы. Например, текстура картины, находящейся внутри здания – она будет отсечена как невидимая. Эту текстуру имеет смысл загружать только тогда, когда картина попадает в поле зрения игрока.
Преимущества:
– позволяет ещё больше сэкономить память;
– еще сильнее уменьшается объем загружаемых данных зоны;
Недостатки:
– приводит к фрагментации памяти;
– недетерминированный порядок чтения (много DVD Seek);
– сложная логика взаимодействия кэшей. Для загрузки зоны 2 недостаточно памяти. Какой из кэшей должен освободить память?
– невозможно сформировать четкие требования к объему арта.

На последнем пункте хочу остановиться поподробнее. Поскольку ресурсы загружаются только по надобности, то становится невозможно четко сформулировать требования по максимальному количеству треугольников, текстур, моделей для конкретной зоны. Приходится либо ставить жесткие требования, рассчитывая на самый плохой случай, либо полностью полагаться на тестирование (здесь игра «тормозит», поэтому нужно упрощать). Учитывая, что совершенствование «движка» идет параллельно с разработкой контента, отсутствие четких требований может привести к многократной переделке контента (а может сейчас тормозит, потому что «движок» недоделан?).

Класс Streamable resource

Для того, чтобы правильно реализовать систему приоритетов загрузки, контроль объёма памяти, неконкурирующий доступ к диску, контроль использования процессорного времени – фоновой загрузкой всех видов ресурсов должен заниматься специальный менеджер. Поэтому все классы ресурсов должны наследоваться от базового абстрактного класса TSreamableResource. Рассмотрим интерфейс этого класса.

В играх, которые загружают весь контент при старте уровня, в процессе игры все экземпляры классов необходимых ресурсов созданы и доступны. Удобно сохранить этот подход и при введении поддержки стриминга.
Например, все экземпляры классов TTexture создаются ещё при инициализации “движка”. Экземпляры хранят описание ресурса, однако в начальном состоянии они готовы только к связыванию, но не использованию.

Итого, интерфейс класса TStreamableResource должен содержать:
– метод опроса состояния: не загружен, загружен, загружается в данный момент;
– метод StartBackgroundLoad() – подать сигнал менеджеру ресурсов о том, что нужно стартовать фоновую зарузку этого ресурса;
– метод Unload() – подать сигнал менеджеру ресурсов о том, что нужно выгрузить этот ресурс из памяти. Поскольку выгрузка не требует много времени, она происходит немедленно в методе Unload();
– метод, возвращающий приоритет ресурса (для стратегии 3);
– методы поддержки refCount(для стратегий 2 и 3). В случае стратегии 3, «движок» должен проверять наличие в памяти и «лочить» всю группу ресурсов, необходимую для выполнения операции. Например, для рисования модели нужен Drawist и 3 текстуры. Если «движок» проверит наличие Drawlist в памяти, но не «залочит» его, то в момент опроса состояния текстур, менеджер будет вынужден загрузить текстуру, и одновременно может выгрузить Drawlist для освобождения памяти. Замечу, что такая ситуация может возникнуть только тогда, когда игровой код заставляет «движок» нарисовать модель, несмотря на провал FPS из-за ожидания загрузки текстуры (это приемлемо, например, при отображении меню).
– методы поддержки времени последнего использования (lastUsedOnFrameId – «последний раз использовался на кадре Id») (для стратегии 3). Небходим для нахождения наболее подходящего кандидата для выгрузки,в случае недостатка памяти.

Эффективное чтение на фоне

CPU и DVD являются независимыми устройствами и могут работать параллельно. Чтобы достичь максимальной скорости загрузки ресурсов, необходимо, чтобы чтение с диска и обработка (паспаковка) прочитанных данных происходили одновременно. Для этого нужно создать два дополнительных потока (thread).

Рисунок 9. Работа потоков «движка».

Поток загрузки занимается обработкой очереди чтения с диска. Этот поток 99% времени блокирован I/O операцией, почти не использует процессорного времени, и поэтому даже может выполняться на том же ядре, что и основной поток «движка».

Поток обработки (resource init thread)занимается подготовкой ресурсов к использованию: распаковкой, инициализацией.

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

Фоновый поток полностью подготавливает ресурс к использованию, но не «подключает» его к «движку». Основной поток его не «видит», пока менеджер ресурсов не проведет «финализацию».

Такой дизайн также упрощает отладку, поскольку ресурсы с точки зрения основного потока не появляются и не исчезают «вдруг».

Стратегии предварительной загрузки ресурсов зоны

К моменту использования ресурс обязан быть в памяти, иначе «движок» будет вынужден именно в этот момент инициировать загрузку и дождаться ее окончания, что вызовет провал FPS. Загрузка необходимых ресурсов должна быть инициирована заранее. Следует заметить, что при использовании стратегии 3, предварительная загрузка не обязательна – об этом речь будет идти чуть позже.

Возможными критериями старта предварительной загрузки могут являться:
– приближение к зоне (по расстоянию до зоны);
– триггер, вручную установленный дизайнером;
– на основе статистики (для стратегии 3). Если в прошлый раз текстура «stone39» понадобилась в точке X,Y,Z мира, то при приближении к этой точке, с определённого расстояния, можно инициировать предварительную загрузку (недостатки статистических методов очевидны, описывать здесь не буду);
– различные эвристики, вручную описанные программистами или дизайнерами. Например, если в поле зрения находится автомобиль, игрок может его взорвать. Необходимо загрузить поврежденную модель и ее текстуры. Если игрок переключается на гранатомет, то нужно загрузить модель ракеты, её текстуры, текстуры дыма и взрыва.

К сожалению, для некоторых видов ресурсов далеко не всегда можно придумать четкие критерии предварительной загрузки, а также обеспечить, чтобы ресурс был в памяти в нужный момент. Для того, чтобы применять стриминг в таких случаях, можно использовать следующие подходы. До тех пор, пока ресурс не загружен:
– используется dummy-ресурс (например, уменьшенная версия текстуры). Dummy-ресурс должен быть в памяти всегда. 5000 текстур размера 64×64 DXT1 занимают в памяти примерно 50Мб. На PC такое количество текстур можно загрузить при инициализации игры;
– объект просто не отображается. Например, если модели отображаются в радиусе 600м вокруг игрока – ничего страшного, если реально модель начнёт отображаться с 500м. Вполне возможно, что даже на таком расстоянии она всё ещё будет скрыта туманом.

Для некоторых типов ресурсов невозможно создать dummy-ресурс: звуки, collision mesh. Для этих ресурсов нельзя применять стратегию 3. Такие ресурсы либо должны загружаться при инициализации игры, либо при загрузке зоны, используя стратегии 1 и 2.

Что же делать, если нельзя использовать загрузку по надобности, а объем ресурсов очень большой для загрузки всего при инициализации игры? Существует одно практическое решение: хранить ресурсы в памяти в упакованном виде(например, Zip), и распаковывать при необходимости. Распаковка намного быстрее загрузки с диска, и не вызывает провал FPS. К примеру, на некоторых платформах из-за требования alignment, текстура может занимать 4Кб, независимо от размера (32×32 и меньше). При этом исходные данные для создания текстуры 32×32 DXT1, даже без упаковки, занимают всего ~700 байт.

Приведу примеры dummy-ресурсов. Текстура – уменьшенная версия текстуры. Модель – billboard, нижний LOD. Звук – моно, низкий битрейт, упакованный с большой потерей качества. Музыка может не проигрываться, пока не загрузится.

Фрагментация памяти

Борьба с фрагментацией памяти – тема для отдельной статьи. Касательно стриминга можно отметить, что стратегия 1 является наиболее эффективной для сокращения фрагментации. Зона является линейным блоком, что позволяет применять даже загрузку подготовленного блока памяти и fixup данных. Этим объясняется популярность стратегии 1 на портативных консолях.

Наиболее сложная ситуация с фрагментацией памяти возникает при использовании стратегии 3. В данном случае можно предложить custom allocators, выделяющие блоки памяти в зависимости от размера из разных pool (чтобы блок памяти в 4 байта не разбивал линейный блок свободной памяти 50Мб пополам), использовать pool для ресурсов одинакового типа, использовать handle-based указатели и сборку мусора.

Очень хорошо помогает использование «слотов» для уникальных данных зоны. Если предполагается, что в памяти должно быть две зоны, и ещё одна должна загружаться, в оперативной памяти резервируются три «слота» одинакового размера, в которые в дальнейшем загружаются уникальные данные зон.

Насколько большой может быть зона?

Очевидно, что все «активные» зоны и их общие ресурсы должны помещаться в оперативную память. Если зоны загружаются в «слоты», максимальный объем зоны жёстко лимитируется размером «слота».

Но кроме объема доступной памяти, нужно также учитывать скорость движения игрока и скорость загрузки зоны с диска.

Приведу пример – гоночный симулятор. Зона представляет собой сегмент дороги длиной 0.5 км. Допустим, максимальная скорость движения машины – 100км/час. Следующий сегмент дороги должен быть в памяти, когда до него остается 0.1 км. Таким образом, загрузка зоны с диска не должна происходить дольше, чем 14,5 сек (0.4км / 100км/ч). Минимальная скорость чтения с DVD диска – 6Мб/сек. За 14,5с мы успеваем прочитать всего 87МБ, и то, без учета времени позиционирования головки привода, и из расчёта, что зона располагается на диске как линейный блок.

В данном случае, очевидно, что размер зоны будет лимитирован скорее скоростью чтения с диска, чем общим объёмом оперативной памяти консоли (512 Мб). И поэтому нас остро начинают интересовать физические характеристики устройств хранения:)

Физические характеристики HDD/DVD/Blue Ray

Рисунок 10. Физические характеристики устройств хранения [6].

К вышеприведенным данным нужно добавить, что DVD привод может снижать скорость, если диск плохо читается.

Прежде чем начать чтение, любой привод должен позиционировать головку на начало данных (seek). Кроме того, что головка имеет определённый вес, и, соответственно, инерцию, замедляющую разгон, привод «толкает» головку в нужном направлении наугад, рассчитывая силу толчка с помощью различных эвристик.

Позиционирование головки представляет собой несколько последовательных «толчков», за каждым из которых следует чтение данных и проверка, «где я нахожусь?». Именно поэтому seek занимает достаточно ощутимое время.

Отсюда вытекает правило: максимизация скорости чтения состоит в сокращении времени, затрачиваемого на seek.

Рассмотрим пример. Средняя скорость чтения – 12 Mb/s. Один seek продолжительностью 120ms приводит к потере 12*0.12 = 1.44 Mb/s от идеальной скорости чтения. Один seek и layer change (200ms) – потеря 2.4MB/s. Spin up – 2 секунды – 24Mb/s потеряно.

Допустим, нам нужно прочитать файл длиной 0.5 MB. Для чтения файла потребуется 3 seek: позиционирование на каталог, позиционирование на FAT, позиционирование на сами данные. Итого, мы получаем скорость 7.68Mb/s из 12Mb/s идеальных. Если файл находится глубоко в дереве подкаталогов, может понадобиться больше позиционирований, и, соответственно, получится меньше скорость.

Оптимизация чтения с DVD

Итак, основная задача при чтении с DVD/Blue Ray – минимизация количества Seek.

Рисунок 11. Механизм HD-DVD привода [7].

Если seek невозможно избежать, то желательно, чтобы головка позиционировалась на соседние дорожки, так как короткий seek происходит быстрее (в пределах N дорожек DVD-привод даже может просто наклонить лазер, не двигая всю головку).

Вторая задача увеличения скорости загрузки ресурсов – банальное уменьшение объема данных.

Для этого можно применять несколько разнообразных технологий.

Компрессия ресурсов одновременно и уменьшает их объём (увеличиваем скорость линейного чтения), и укорачивает seek (ресурсы становятся меньше, и, соответственно, сдвигаются ближе друг к другу. Другими словами, в упакованном виде игра занимает 1/2DVD диска, а в пределах полдиска головка двигается быстрее).

Групповые файлы позволяют избавиться от парсинга дерева подкаталогов (лишних seek), приблизив количество позиционирований к идеальной единице. Дополнительно, групповые файлы «уменьшают» общий объем ресурсов на диске. DVD сектор имеет размер 2Кб. Файловая система выделяет под файл один кластер, который состоит из одного или более секторов. Таким образом файл, занимающий всего 1 байт, реально занимает на диске 2Кб. Групповой файл позволяет избавиться от выравнивания начала файлов на границу кластера, располагая ресурсы максимально близко друг к другу (сокращается длина seek).

Если располагать зависимые ресурсы физически близко на диске, средняя длина seek при загрузке значительно сокращается. Например, текстуры модели должны располагаться сразу за ее геометрией. Физическое размещение файлов можно задавать, редактируя DVD layout, однако это проще делать, используя групповые файлы.

Некоторого снижения количества Seek можно достичь дублированием файлов на диске. Например, если две разные модели используют одну текстуру, можно расположить данные этой текстуры сразу за данными и первой, и второй модели. Для DVD (8Гб), этот способ не всегда приемлем из-за значительного увеличения размера игры на диске, но для BlueRay вполне применим. Однако, такую оптимизацию нужно использовать осторожно, так как увеличение размера игры одновременно увеличивает random seek, что наоборот может привести к снижению производительности. К сожалению, мне не известен какой-либо универсальный алгоритм для определения, насколько эффективно дублирование конкретного файла.

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

И наконец, если консоль имеет HDD, то она обычно предоставляет специальный раздел на диске, который можно использовать для кэширования данных с DVD. Игра не имеет возможности скопировать вест контент на HDD при старте, т. к. копирование 200Мб ресурсов занимает ~20 секунд. Поэтому игра копирует данные при простое DVD (например, во время показа основного меню игры). Как только данные оказываются на HDD, игра уже не использует DVD. Однако, наличие HDD может быть опциональным, поэтому такой подход позволяет уменьшить только время начальной загрузки и время загрузки сохраненных игр для игроков, у которых есть HDD. Сриминг мира должен работать нормально и с DVD.

Компрессия

Существует множество готовых библиотек компрессии, которые можно использовать для сжатия ресурсов. Для сжатия обычных данных можно применять Zlib или LZO. LZO сжимает хуже (+20% по сравнению с ZIP), но зато может распаковывать данные «in place» – запакованные данные загружаются в «слот» и там разжимаются, без выделения дополнительной памяти. Это полезное свойство позволяет эффективно использовать LZO в стратегии 1.

Для текстур лучше использовать DXT/JPEG, или ZLIB поверх DXT. Для звуков – mp3, ogg.

Если считывание с диска и распаковка происходят параллельно, это может повысить эффективную скорость считывания почти в два раза.

Пример.
Чтение:12 Mb/s, Распаковка: 30Mb/s (ZIP), Compression rate: 0.6. Если сначала происходит считывание, а потом распаковка – эффективная скорость чтения: 14.3Mb/s. При параллельной распаковке: 20Mb/s.

Xenus 2: White Gold. Стратегия загрузки.

Рисунок 13. Xenus 2: Белое золото [8].

Мир Xenus 2 поделен на квадраты 600×600м (зоны). Уникальными ресурсами зон являются: геометрия ландшафта, построек; collizion mesh, структуры AI, освещение статической геометрии (lightmaps), вершинное освещение моделей. Общими ресурсами(независимый кэш) являются: текстуры, модели, анимация, звуки, музыка, импосторы уровней(упрощенная геометрия уровней для отображения на горизонте). При инициализации игры загружаются: шейдеры, строковые ресурсы(диалоги), hitmesh моделей(упрощенный меш для расчета попаданий при стрельбе), collison mesh моделей, описания систем частиц.

В памяти находятся зоны, которые пересекаются с bounding box 1100×1100м вокруг игрока (то есть 4 ближайшие зоны). Дизайнер может указывать группы зон, которые должны быть в памяти цельно. Например, если на уровне находится верхушка горы, то желательно загрузить её пораньше. Контент построен таким образом, чтобы в любой точке мира суммарный объём уникальных данных загруженных зон не превышал фиксированный бюджет.

Под текстурный кэш выделен фиксированный объём памяти. Кэш никогда не загружает текстуры, если бюджет использован. Уменьшенные версии всех текстур (не больше 64×64) загружаются при инициализации игры. Для 5000 текстур это примерно 50Мб.

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

Чтобы сократить количество нежелательных эффектов, используются эвристики: зависимость ресурсов (автомобиль + взорванный автомобиль), модель оружия – модель гильз и т.д. Некоторые модели и текстуры «залочены» в кэше, например, некоторые текстуры HUD.

Для минимизации seek используются групповые файлы (100Mb – 2GB). Создаются отдельные групповые файлы для ресурсов разного типа (отдельно для текстур, моделей, уровней). Таким образом, данные для ресурсов с одинаковым приоритетом находятся максимально близко друг к другу.

При загрузке, запросы сортируются для уменьшения seek.

Для дальнейшего уменьшения средней длины seek на этапе построения групповых файлов создается граф со связями между файлами (текстура-текстура (в пределах модели), модель-модель(в пределах зоны), модель-модель (зависимые модели), звук-звук (в пределах зоны) и т.д). Чем сильнее связь, тем ближе должны быть файлы.

Оптимизация размещения файлов сводится к минимизации оценочной функции:

S = Summ ((k*distance(f1,f2))^2 )

где k – “толщина” связи (каждая зависимость прибавляет +1 к “толщине”)
distance(f1,f2) – расстояние между абсолютными позициями начал файлов f1 и f2 (в байтах).

Из-за огромного размера графа (~5000 узлов в графе, ~ 50000 связей) найти оптимальное решение в разумные сроки не представляется возможным, поэтому используется генетический алгоритм. Работа алгоритма ограничена временем – мы используем 2 часа на каждый групповой файл, что позволяет получить информацию для оптимизации за одну ночь. Экспериментально проверено, что через 2 часа работы оценочная функция перестает стремительно уменьшаться.

Алгоритм умеет вставлять дубликаты файлов, но увеличение размера группового файла не должно превышать 20%.

Минимизация оценочной функции не производится каждый раз при сборке билда. Решение сохраняется специальный файл, и используется много раз для построения оптимизированных групповых файлов. В идеале, решение может периодически обновляться автоматически на выделенной машине.

Текстуры сжаты в DXT1/DXT5, сверху ZIP. Звуки и музыка сжаты в ogg, распаковываются в реальном времени. Остальные ресурсы сжаты ZIP.

Оптимизация чтения с DVD: с чего начать?

Если отсортировать все приведенные методы по реальному влиянию на скорость загрузки, получится следующий список:
– сортировка очереди загрузки ресурсов;
– параллельная распаковка и чтение с DVD;
– групповые файлы;
– компрессия ресурсов;
– оптимизация размещения файлов внутри группового файла;
– вставка дубликатов файлов.

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

 

Заключение и планы на будущее

К сожалению, оптимизация загрузки с DVD относится к классу задач с нечеткими критериями результата. Выполнена задача, или нет – можно определить только тестированием игры. В тот момент, когда отдел тестирования перестает жаловаться, что игра «тормозит», можно считать моментом достижения положительного результата (а возможно, они просто устали жаловаться?:). Единственные цифры, которые я могу привести, это время начальной инициализации игры, которое удалось сократить с 2 минут до 30 секунд.

В настоящее время мы расширяем систему загрузки ресурсов под распараллеленный «движок». Например, перед финализацией ресурсов происходит синхронизация всех потоков, которые используют ресурсы – в определённые моменты все потоки временно останавливаются, чтобы один из потоков мог выполнить финализацию. Это позволяет продолжать работать с ресурсами без критических секций. С введением параллельного рендеринга, все ресурсы получили поле RefCount, чтобы любой поток мог «залочить» ресурс в кэше на время использования.

В целом, после реализации описанных оптимизаций, работа над менеджером ресурсов завершена, поставленных целей мы добились.

Ссылки

1. Highly Detailed Continuous Worlds: Streaming Game Resources From Slow Media
http://www.gamasutra.com/features/gdcarchive/2003/Denman_Stu.ppt

2. Streaming for Next Generation Games
http://www.gamasutra.com/view/feature/1769/streaming_for_next_generation_games.php?print=1

3. It’s Still Loading?
http://www.drizzle.com/~scottb/gdc/its-still-loading.ppt

4. PS3 Oblivion Seeing Double To Counteract Blu-Ray
http://www.gamesetwatch.com/2007/01/ps3_oblivion_seeing_double_to.php

5. Blog debate: PS3 to load games slower than the Xbox 360
http://www.joystiq.com/2006/09/04/ps3-to-load-games-slower-than-the-xbox-360/2

6. Is Blu-Ray better for Games
http://forum.beyond3d.com/showthread.php?t=37751

7. Война форматов 2: HD DVD наступает под флагом компании NEC, но пока только в области DVD-ROM
http://www.ixbt.com/optical/nec-hr1100.shtml

8. Официальный сайт игры Xenus 2: Белое золото
http://whitegold-game.com/

9. Материалы лекции

http://www.deep-shadows.com/hax/downloads/DVDReading.7z

10. Аудиозапись лекции
http://www.deep-shadows.com/hax/downloadsKRI_2008_Programming_20apr_saturn_04_Lut_Roman_Deep_Shadows.ogg