382
Грязный флаг (Dirty Flag) —
Паттерны программирования игр
Замараем руки
Итак, код отрисовывает меши в нужных местах, но не са-
мым эффективным способом. Он вызывает
local_.
combine(parentWorld)
для каждого узла графа
на каждом кадре. Давайте посмотрим, как это испра-
вить с помощью нашего паттерна. Прежде всего нам
надо добавить два поля в класс
GraphNode
:
class GraphNode
{
public:
GraphNode(Mesh* mesh)
: mesh_(mesh),
local_(Transform::origin()),
dirty_(true)
{}
// …
private:
Transform world_;
bool dirty_;
// …
};
В поле
world_
кэшируется предыдущее значение гло-
бальных преобразований, а
dirty_
, конечно же, грязный
флаг. Обратите внимание: изначально флаг имеет значе-
ние
true
. Когда мы создаем новый узел, его глобальные
преобразования еще не посчитаны. При рождении он уже
не синхронизирован с локальными преобразованиями.
Единственная причина, для чего мы используем дан-
ный паттерн: предметы могут
двигаться
. Давайте доба-
вим соответствующую поддержку:
void GraphNode::setTransform(Transform local)
{
local_ = local;
dirty_ = true;
}
Паттерны программирования игр
— Паттерны оптимизации
383
Важно: сразу устанавливаем грязный флаг. Мы ниче-
го не забыли? Конечно, дочерние узлы!
Когда родительский узел перемещается, координаты
его дочерних элементов тоже изменяются. Но здесь мы
не устанавливаем их грязные флаги. Мы
могли бы
это
сделать, но тогда из-за рекурсии снизится скорость. Луч-
ше мы сделаем вот так:
void GraphNode::render(Transform parentWorld,
bool dirty)
{
dirty |= dirty_;
if (dirty)
{
world_ = local_.combine(parentWorld);
dirty_ = false;
}
if (mesh_) renderMesh(mesh_, world_);
for (int i = 0; i < numChildren_; i++)
{
children_[i]->render(world_, dirty);
}
}
Код напоминает нашу начальную реализацию. Глав-
ное изменение в том, что мы проверяем грязный флаг узла
до начала расчета его глобальных преобразований, а ре-
зультат храним в поле, а не в локальной переменной. Когда
узел чистый, мы пропускаем операцию
combine()
и ис-
пользуем старое, но все еще актуальное значение
world_
.
Хитрость в параметре
dirty
. Он будет принимать
значение
true
, если хотя бы один узел из его родитель-
ской цепочки имеет установленный грязный флаг. При-
мерно так же, как
parentWorld
постепенно обнов-
ляет глобальные преобразования, пока мы идем вниз
по иерархии,
dirty
отслеживает установку флага в ро-
дительской цепочке.
Выбранный способ позволяет нам избежать ре-
курсивного прохода, чтобы установить флаг
dirty_
Тут мы предполагаем,
что проверка с помо-
щью
if
быстрее, чем пе-
ремножение матриц. Ко-
нечно, один бит намного
быстрее, чем набор ма-
тематических вычисле-
ний над числами с пла-
вающими точками.
Тем не менее совре-
менные процессоры
удивительно сложные.
Они сильно полагаются
на
конвейерную обра-
ботку
— помещение се-
рии последовательных
инструкций в очередь.
Большое количество
if
может привести
к боль-
шому количеству ошибок
предсказания
и заставить
тратить ресурсы на пе-
рестроение конвейера.
В главе «Локальность
данных (Data Locality)»
(с. 343) рассказывается
подробнее о желании
процессоров работать
быстрее и о том, как вам
не попасть в такие не-
приятные ситуации.
Обратите внимание: это
работает только потому,
что
render()
— един-
ственный метод в классе
GraphNode
, которому
нужна актуальная ин-
формация о глобальных
преобразованиях.
Если бы их использо-
вали и другие, нам при-
шлось бы идти другим
путем.
Do'stlaringiz bilan baham: |