Некоторые игры используют многопоточные рендеры в качестве основной философии дизайна.
Например ... поток 1 вычисляет всю игровую логику, а затем отправляет эту информацию в поток 2. Поток 2 предварительно вычисляет список отображения и передает его в графический процессор. Поток 1 в конечном итоге запускает 2 кадра за графическим процессором, поток 2 выполняет один кадр за графическим процессором.
Преимущество в том, что вы теоретически можете выполнять вдвое больше работы в кадре. Скинирование может выполняться на процессоре и может стать «свободным» с точки зрения времени процессора и графического процессора. Для этого требуется двойная буферизация большого объема данных и тщательное построение потока вашего двигателя, чтобы все потоки останавливались, когда (и только когда) это необходимо.
Помимо этого, в наши дни довольно распространенным методом является запуск нескольких «рабочих потоков». Задачи с общим интерфейсом могут быть добавлены в общую (потокобезопасную) очередь и выполнены рабочими потоками. Затем основной поток игры добавляет эти задачи в очередь до получения необходимых результатов и продолжает выполнение другой обработки. Когда результаты в конечном итоге требуются, основной поток может останавливаться до тех пор, пока рабочие потоки не завершат обработку всех необходимых задач.
Например, дорогой цикл for можно заменить на используемые задачи.
// Single threaded method.
for (i = 0; i < numExpensiveThings; i++)
{
ProcessExpensiveThings (expensiveThings[i]);
}
// Accomplishes the same work, using N worker threads.
for (i = 0; i < numExpensiveThings; i++)
{
AddTask (ProcessExpensiveThingsTask, i);
}
WaitForAll (ProcessExpensiveThingsTask);
Вы можете сделать это, когда вам гарантировано, что ProcessExoyThings () является поточно-ориентированным по отношению к другим вызовам. Если у вас есть 80 вещей на 1 мс каждый и 8 рабочих потоков, вы сэкономили примерно 70 мс. (Ну, не совсем, но это хорошее волнообразное приближение.)