Написал аддон для ReShade, который ускоряет DLSS 5 Neural Rendering: +50~75 % fps

Написал аддон для ReShade, который ускоряет DLSS 5 Neural Rendering: +50~75 % fps

DLSS 5 Neural Rendering выглядит круто, как я считаю: нейросеть дорисовывает освещение и материалы поверх готового кадра, волосы персонажей - вообще шикарными становятся. Но модель съедает 13~15 мс на кадр в 4K даже на RTX 4080 SUPER. Я неделю пытался сократить время её работы, не трогая саму модель. Получился Optimizer FPS for DLSS5: аддон для ReShade с открытым кодом (MIT).

Две идеи

Первая идея: сжать периферию(обычно туда не смотрят, там UI и край экрана). Центр кадра (80 % по каждой оси) остаётся как есть, а края нелинейно сжимаются, так что кадр перед моделью становится 90 % по ширине и 90 % по высоте. Это 81 % от исходного числа пикселей. После модели края растягиваются обратно. (можно настраивать и сдвигать)

Стенд. Голубая рамка: центр 1:1, всё снаружи сжимается перед моделью. Оранжевая: граница рабочего кадра, который видит модель.
Стенд. Голубая рамка: центр 1:1, всё снаружи сжимается перед моделью. Оранжевая: граница рабочего кадра, который видит модель.

Вторая идея: считать модель не каждый кадр. Модель запускается каждый второй, третий или четвёртый кадр; дальше этот интервал обозначаю как N. Промежуточные кадры не пропускаются. Вклад модели, то есть разница между кадром до и после неё, переносится по векторам движения самой игры (они говорят, куда сдвинулся каждый пиксель за кадр), а проверки по глубине и цвету отбрасывают то, что перестало совпадать. Есть и фоновый режим: модель работает на своей очереди GPU на пару кадров позади, время кадра получается ровнее.

Это не генерация кадров, как у DLSS, FSR или XeSS. Там движок уже отрисовал следующий кадр, и между ним и предыдущим дорисовывается промежуточный, тот самый «фейковый кадр»: показ настоящего откладывается, отсюда рост задержки ввода и вязкость управления. Идея была: получать настоящие кадры без отставания. Каждый показанный кадр только что отрисован движком, и на него натягивается вклад модели с прошлого кадра. Будущее знать не нужно, кадры не задерживаются, управление не становится вязким, но появляется другая проблема.

Дыры и зубы

При переносе результата из прошлого кадра, репроекции, сложнее всего с участками, которых раньше не было видно. Камера повернулась, из-за угла показался кусок стены, персонаж резко открыл рот или сильно гримасничает. Модель эти пиксели ещё не видела, переносить нечего, и без заполнения там оставался сырой кадр движка: пятно другой яркости.

Первый вариант заполнения брал размытый вклад модели вокруг точки: пятно уходило, но на контрастных краях появлялись шлейфы. Сейчас, если проверка отвергла перенос для пикселя, алгоритм ищет рядом точки с той же глубиной и близким цветом и берёт вклад модели из них. Затем подстраивает его под яркость и цветность заполняемого пикселя, чтобы не переносить чужой оттенок. На стенде это убрало шлейфы за движущимися объектами и мерцание травы.

Промежуточный кадр, рот: слева результат, справа карта проверок после переноса по векторам движения. Синее: вклад модели принят. Чёрное: отброшено по глубине или цвету, сюда идёт заполнение.
Промежуточный кадр, рот: слева результат, справа карта проверок после переноса по векторам движения. Синее: вклад модели принят. Чёрное: отброшено по глубине или цвету, сюда идёт заполнение.

Зубы победить до конца не удалось. Когда рот открывается, на промежуточных кадрах внутри рта примерно на 10 % светлее, чем при полном проходе модели: она затемняет полость сильнее, чем можно предсказать по любому кадру с закрытым ртом. Все варианты заполнения давали в этом месте одно и то же, помогает только более ранний полный проход. Поэтому рекомендую N=2~3. Дефект держится один-два кадра и заметен только на очень быстрых движениях, а их почти не бывает: при 40~60 fps за один-два кадра объекты редко успевают настолько сильно сместиться в кадре.

Кадр 317 со стенда: слева модель каждый кадр, справа промежуточный кадр при N=8 сразу после открытия рта. N=8 взят, чтобы дефект был виден: полоса зубов светлее, граница идёт по старой линии губ. При N=2~3 он держится один-два кадра.
Кадр 317 со стенда: слева модель каждый кадр, справа промежуточный кадр при N=8 сразу после открытия рта. N=8 взят, чтобы дефект был виден: полоса зубов светлее, граница идёт по старой линии губ. При N=2~3 он держится один-два кадра.
Кадр 317 и маска новой поверхности: пиксели, у которых глубина и цвет не совпали с кадром последнего полного прохода после переноса по векторам движения. Пятно совпадает с полостью рта.
Кадр 317 и маска новой поверхности: пиксели, у которых глубина и цвет не совпали с кадром последнего полного прохода после переноса по векторам движения. Пятно совпадает с полостью рта.

Три стенда

Проверять такое в живой игре неудобно: сцена не повторяется, а renodx пересоздаёт модель при каждом переключении. Стендов в итоге получилось три.

Первый, самый маленький, это программа, которая загружает только перехватчик аддона и заглушку вместо модели, под отладочным слоем D3D12. На нём ловились ошибки состояний ресурсов и дескрипторов, до которых в игре не докопаться.

Второй, основной: маленькая игра на D3D11 с процедурной сценой из шахматного пола, коробок, движущегося персонажа и камеры по скрипту. Поверх неё стоит та же цепочка, что в Baldur's Gate 3 (В первую очередь я именно в ней захотел чтоб всё работало, когда-нибудь я пройду её 🙂 ), но без игры: настоящий DLSS через мост и нейрорендер через renodx поверх ReShade. Стенд снимает кадры в файлы и мерит среднее отклонение промежуточных кадров от прогона «модель каждый кадр», так что каждая правка проверялась числом, а не на глаз. Позже добавилась загрузка glTF, и голова с анимацией, т.к. в диалогах в Baldur's Gate 3 и нашлась история с зубами.

Голова с анимацией рта на стенде: на ней воспроизводится случай с зубами. Модель cutegirl (Sketchfab, CC BY-NC-SA), фон HDRI-панорама.
Голова с анимацией рта на стенде: на ней воспроизводится случай с зубами. Модель cutegirl (Sketchfab, CC BY-NC-SA), фон HDRI-панорама.

Третий стенд повторяет второй, но с родным D3D11-путём DLSS без моста, потому что у многих игр цепочка устроена именно так и состояния ресурсов там другие.

Второй стенд лежит в репозитории вместе со скриптами замера.

Что в итоге

Артефакты полностью никуда не делись: лёгкие шлейфы при резких поворотах камеры, небольшие мерцание мелких деталей с частотой проходов, отставание тона на один кадр при резкой смене освещения.

Сам играю в синхронном режиме с N=3: модель считается каждый третий кадр, артефакты на этом уровне почти не заметны. С этими настройками на RTX 4080 SUPER в 4K частота кадров в Baldur's Gate 3 поднялась с 37 до 65 fps, в Jedi: Fallen Order с 30 до 45. Дальше уже нужно смотреть по игре, в The Surge и режим N=8 удивительно хорошо себя показывает. В идеале считаю что хорошо хотя бы поднять до 45, чтоб управлялось нормально, и там уже даже Smooth Motion через драйвер хорошо показывает себя, тем более если базовый fps больше 60.

Демонстрация в играх

Что нужно

Уже работающий нейрорендер (renodx-dlss5, DLSS5-Feeder для 32-битных игр или форк OptiScaler с DLSSNR), ReShade 6.8+ с поддержкой аддонов, nvngx_dlssnr.dll 310.8. Проверял только на 4080 SUPER; сам нейрорендер запускают и на RTX 20-й серии, но свой аддон на этих картах я не проверял. Аддон ставит хуки на DLSS внутри процесса игры, поэтому в играх с античитом использовать нельзя(как и вообще ReShade).

Установка: распаковать zip, перетащить exe игры на Install-OptimizerFPS.cmd, запустить игру. Вкладка появится в оверлее ReShade, раздел Add-ons.

Вкладка аддона в ReShade: режим, зона, каденция модели.
Вкладка аддона в ReShade: режим, зона, каденция модели.

GitHub: https://github.com/BeliyG3/optimizer-fps-dlss5

34
8
6
5
2
1
1