CodeWhiz & Player1
CodeWhiz CodeWhiz
Привет, покопался тут на днях с Unity profiler'ом, нашел несколько классных способов сэкономить миллисекунды в игровом цикле. Хочешь поделимся опытом, как лучше поддерживать стабильные FPS?
Player1 Player1
Звучит круто! Давай, начинаем – я тут уже не первый день вожусь с батчингом, куллингом и GC-пиками. А какой твой самый надежный способ?
CodeWhiz CodeWhiz
За мной всегда в приоритете – следить за циклом обновлений. Тяжелые вычисления перекладываю на Burst-компиляцию, использую статический батчинг для статических мешей, и обязательно пулинг объектов вместо Destroy/Instantiate. Потом включаю профайлер, чтобы выловить скрытые GC-пики, подкручиваю размеры пакетов и настраиваю дальности отсечения, пока фрейм-тайм не станет стабильным. А у тебя как, на что в основном ориентируешься?
Player1 Player1
Круто! Я за то, чтобы рендер-пайплайн был спокойным – обычно добавляю GPU instancing и слежу, чтобы основной поток не грузил себя огромными мешами. Еще я заранее подгружаю текстуры и быстро проверяю скрытые DrawCalls, чтобы GPU не выходил из себя. А у тебя с Burst jobs как? Какие приемы используешь, чтобы они были эффективными?
CodeWhiz CodeWhiz
Отличная организация пайплайна. Для Burst-задач стараюсь держать всё в минимальном объёме: использую NativeArrays с пулом вместо выделения на каждый кадр, применяю IJobParallelForBatch, чтобы компилятор разбивал работу на куски по 64 элемента, и слежу за тем, чтобы все данные находились в непрерывных структурах – чтобы SIMD заработал. Ещё избегаю боксирование и дженерики внутри задачи, делаю функцию статической и использую атрибут BurstCompile с опциями для инлайна небольших вспомогательных функций. Главное – профилировать задачу через Burst-профайлер и убирать лишние копирования памяти, которые не затрагивают GPU. А как ты решаешь задачу потоковой загрузки текстур в цикле?
Player1 Player1
За текстурами я, знаешь, как будто в "беспредел" играю. У меня в очереди висят цепочки текстур с низким разрешением, которые загружаются в фоне, пока основной поток занимается логикой. Использую асинхронную загрузку, чтобы сразу же отправлять данные на GPU, как только они готовы, чтобы кадры не зависали. И ещё одно правило: текстуры не должны задерживаться на стороне CPU дольше, чем один кадр, иначе получишь неприятные просадки. Да и небольшой пул "пустых" текстур держу, чтобы быстро менять их при переходе на новый уровень. А какие у тебя есть хитрости, чтобы текстуры не съедали всю оперативку?
CodeWhiz CodeWhiz
Обычно я слежу за использованием памяти текстур, сжимая всё в ASTC или ETC2, если платформа это поддерживает, а потом сразу удаляю неиспользуемые слоты атласа. Ещё я устанавливаю жёсткий лимит на общий объём памяти GPU, используемый текстурами, и запускаю небольшой фоновый поток, который удаляет наименее используемые атласы до того, как лимит будет достигнут. При переходе между уровнями я пересобираю атлас "на лету", используя только те ассеты, которые реально видны, и оставляю небольшой "резервный" атлас для интерфейса или заполнителей, чтобы не тратить место на пустые тексели. Ну и обязательно вызываю Release для текстур сразу после смены материала, чтобы драйвер мог освободить память. А как организован твой пайплайн сжатия?
Player1 Player1
Крутые приемы! Для сжатия я просто использую встроенные ASTC / ETC2 в Unity, но я заставляю выполнить быстрый предварительный этап сжатия прямо в редакторе, чтобы не было узких мест при сборке. Еще у меня есть небольшой скрипт, который "на лету" переводит текстуру в ASTC, если платформа это поддерживает, и сразу же запускает цепочку мип-карт после импорта. Так я могу заменить большую текстуру в сцене, не вызывая тормозов на всю игру. Есть какие-нибудь лайфхаки для этого раздражающего спрайта, который никак не влезает?