Arseny Kapoulkine:

Advanced Visual Effects with DirectX 11: Real Virtual Texturing - Taking Advantage of DirectX 11.2 Tiled Resources

Speaker: Cem Cebenoyan (NVIDIA)
DirectX оказывается еще не умер, и прямо даже есть какие-то свежие фичи (которые здесь уже несколько раз обсуждали конечно, но неважно). Так глядишь еще окажется что DirectX 12 это не миф!
Для тех кто не в курсе, очень кратко - есть новый DirectX (11.2, уже год как), в нем есть новая фича - виртуальная память которую может контролировать разработчик.
Собственно виртуальная память говорят в GPU уже много лет как, видимо из-за WDDM - но вот теперь с ней можно работать.
Виртуальная память в контексте GPU означает не HDD swapping, а page translation tables - т.е. можно аллоцировать огромный буфер в адресном пространстве, под который будет отведено мало физической памяти.
Выглядит это так - вы аллоцируете текстуру например 16k x 16k со специальными флажками, а дальше руками задаете page mapping из виртуального пространства в "физическое" (специальный буфер)
Маппинг страничками, страничка 64 Kb (128x128 RGBA8, 512x256 DXT1)
Странички можно не мапить никуда, можно мапить много виртуальных страничек в одну и ту же физическую.
Есть два варианта поддержки железа: базовый, tier1 -- результат чтения unmapped странички неопределен, результат записи в unmapped страничку не определен (обе проблемы решаются маппингом всех страничек в одну и ту же dummy физическую с контентом который вам нужен), и в шейдере нельзя узнать, семплинг прошел или нет.
И tier2 -- чтение unmapped странички возвращает 0, запись не делает ничего, и в шейдере есть инструкция семплинга которая умеет говорить, был ли доступ к unmapped страничкам или нет.
Прелесть в том что семплинг работает как обычно - т.е. с поддержкой фильтрации, включая анизотропку.
3D текстуры не поддерживаются.
Говорят, tier1 с современными драйверами поддерживают все GPU вышедшие за последние лет 5.
Ну и собственно, нафига козе баян?
Приводится очевидный пример virtual texturing.
То что раньше делалось через indirection texture и требовало жертв в лице анизотропной фильтрации а также делать рамочки внутри тайлов виртуальной текстуры, теперь работает практически само.
Если вы влезаете в максимальный размер виртуальной текстуры, конечно.
Который вроде как все еще 16kx16k.
И приводится еще один пример, sparse shadow maps.
Идея значит примерно в следующем...
Давайте сделаем shadow map 16k x 16k.
Это виртуальная текстура которая занимает, эээ, гигабайт адресного пространства.
Теперь давайте туда будем отрисовывать сцену.
Но некоторые объекты будем отрисовывать не в самый детальный мип уровень,
a в какой-нибудь другой.
Например, если тень от объекта очень далеко,
то достаточно иметь shadow map данные от нее не в полном разрешении.
Это примерно каскадные шадовмапы, только в каскадах есть четкая простая конфигурация, а тут нет.
Если я не ошибаюсь то это похоже на queried virtual shadow maps или может на tiled shadow maps.
Но работает в железе сегодня.
Смотрите, как работает.
Предположим что у нас есть gbuffer pass или depth pre-pass, для которого не нужна shadow map.
Давайте в нем сделаем следующее - сделаем вид что мы семплим shadow map для текущего пикселя, и посчитаем mip уровень который нам для этого нужен.
Это можно сделать тупо вот этой функцией http://msdn.microsoft.com/en-us/library/windows/desktop/bb944001(v=vs.85).aspx
И результирующий mip уровень запишем в gbuffer.
Теперь у нас в каждом пикселе есть:
  • 1. Позиция пикселя в shadow map space (ее нет, но можно посчитать, восстановив позицию через глубину)
  • 2. shadow map mip level.
Shadow map у нас был 16k x 16k, тайлы размером 128x128 т.е. у нас 64х64 тайла.
Сделаем текстуру 64х64, и в compute shader пройдемся по всем пикселям gbuffer и запишем в каждый пиксель этой текстуры (пиксель текстуры соответствует тайлу 0-го мипа shadow map) минимальный mip уровень из gbuffer который нужен для этого тайла.
Это можно сделать с помощью interlocked min.
Минимальный == самый детальный.
Теперь мы про каждый тайл shadow map знаем, какой там нужен mip уровень (логика чуть усложняется тем что тайлы в первом мип уровне это 4 тайла из 0-го, но это детали)
Теперь проаллоцируем первые 1024 тайла, отсортировав их в порядке уменьшения мипа (увеличения качества), чтобы всегда иметь запасной вариант.
Затем отрендерим геометрию в эту shadow map во все мипы разом.
Во все мипы разом геометрия рендерится с помощью геометрического шейдера, размножающего треугольники, или инстансинга + GS - аналоично single pass render to cubemap.
Затем отшейдим сцену как обычно, при выборке данных из shadow map нужно сделать LOD clamping чтобы случайно не посемплить из непроаллоцированной страницы, для этого есть в Tier2 новая функция семплинга.
Все.
Получилась отрисовка сцены в гигабайтный shadowmap, который страничками отмаплен в 64 мб буфер.
Причем мы весь процесс сверху делаем каждый кадр, через что качество адаптивно.
Например если кусок сцены скрыт за стеной.
То мы не выделим на этот кусок подробного shadow map.
Конечно если кусок сцены скрыт за забором с мелкими дырками то все не так радужно, но вот.
Очевидно мы обрабатываем заметно больше геометрии (это частично решается, как мне кажется, более умным GS который спроецирует треугольник в SM space и посмотрит, какой минимальный мип нужен)
Я спросил, скорость они конечно не меряли, демка наверное будет выложена.
Yury Degtyarev:
у меня 128 * 64 != 16k, видимо тайл размера 256x256?
Arseny Kapoulkine:
Нет, тайл всегда 128х128 (64 Kb) - я неправильно поделил, тайлов 128x128.
Yury Degtyarev:
получается, текстуру тайлов 64x64 перегоняют на CPU?
и с чем связана работа с первыми 1024 тайлами? почему не сразу 4096.
Сортируем и откатываемся к предыдущему мипу, если не хватает памяти, я так понимаю (т.е. много тайлов с малым уровнем мипа)? В этом случае надо еще и обновить тайловую текстуру на GPU.
Arseny Kapoulkine:
Да, в презентации это было явно не указано, я хочу уточнить этот момент у автора - выглядит так что в текущей реализации метод требует CPU-GPU sync.
Ну или разумеется можно брать данные с прошлого кадра.
Будут артефакты иногда.
Arseny Kapoulkine:
Ну, смотря сколько есть памяти.
Т.е. пример из презентации - SM на гигабайт, но мы хотим использовать 64 Mb памяти.
64 Mb памяти это 1024 тайла.
Arseny Kapoulkine:
Если нам нужен мип N, то нам нужны мипы N+1..max -- структура которая записывает индексы тайлов трекает только минимальный мип.
Так что если не хватает памяти то сортировка от больших индексов мипов к маленьким все сделает правильно.