Дни со второго по шестой закончились 21 октября в 11:31 на версии 0.24.0: следующим планом стояло освещение, самый большой риск дорожной карты. Этот пост доходит до 27 октября, 18:52, и версии 0.32.0. За это время закрылись десять планов. Теперь движок освещает пещеру светящейся в ней породой и даёт этому свету отражаться. Он снова держит полную детализацию до 100 м в виде по умолчанию. И в нём есть замок.

По календарю отрезок короткий. Работа шла днём 21-го, весь день 22-го и 27-го. С 22:30 22-го до 07:03 27-го в репозитории не было ни одного коммита: у меня кончился недельный лимит. Все замеры кадра и CPU ниже снова сняты на моём ноутбуке, машине разработки из файла бюджетов: RTX 3080 Laptop GPU и Ryzen 9. Числа для Windows — с раннера CI. Движок делается вместе с первой игрой, и этот пост снова не о ней.

Порядок работы тот же, что в первый день. Каждый план идёт по фазам в линии, у агента со своей копией репозитория; фаза с пометкой human стоит, пока я не посмотрю на результат в окне; каждое закрытие проверяет свежий агент; а замеры GPU ждут тихой машины, когда ни одна другая линия её не нагружает. Два слова из прошлых постов: зеркало — это копия мира на GPU, а синхронизация — шаг, который приводит её в актуальное состояние.

Сначала одно изменение плана. 21-го, в коммите, где были набросаны следующие пять планов, я отказался от базовой карты: GPU класса GTX 1660, под который написан бюджет кадра, теперь, как сказано в дорожной карте, «не приобретён», и «нижняя планка остаётся гейтом на бумаге и читается как оценка по двум замерам, снятым на машине разработки». Взамен в файле бюджетов появилась вторая колонка, медленный адаптер: встроенная графика AMD того же ноутбука на драйвере RADV. Начиная с плана 0028 каждый план рендера снимает замеры на обоих GPU.

Свет

План 0024 одобрили на шестой день, но не начали. Начался он 21-го в 14:03 и закрылся 22-го в 08:35 версией 0.27.0, после двух ревью при закрытии.

Проход лучей больше не считает освещение. До 0.24 один compute-проход вёл луч, находил попадание и тут же освещал его налобным фонарём. Теперь кадр — это цепочка проходов, у каждого свой буфер вывода и своя строка в бюджете.

flowchart TB
hit["проход попаданий: на пиксель позиция, нормаль, материал, полупрозрачный слой"] --> light["проход света: налобный фонарь плюс одна грань светящегося вокселя, выбранная из 8 кандидатов; один теневой луч"]
samples["синхронизация зеркала извлекает светящиеся воксели в образцы света, по 16 байт"] --> light
light --> temporal["временной проход: перепроецировать по позиции попадания, забыть историю внутри боксов правок кадра"]
probes["объём зондов: 32 x 32 x 32 зонда, 1/8 обновляется за кадр"] --> gather["сбор непрямого света на пиксель"]
temporal --> atrous["дисперсия, затем четыре шага фильтра a-trous"]
gather --> comp
atrous --> comp["композитинг и тонмаппинг"]

Один проход попаданий занимает 1,235 мс, а старый единый занимал 1,73–1,75. Ядру без шейдинга на AMD хватает 64 регистров, и оно ничего не сбрасывает в память; старому нужно было 84, и оно сбрасывало.

Проход света — это next-event estimation, стандартный приём трассировщика путей: луч целят в источник света, а не надеются в него попасть. Налобный фонарь сэмплируется напрямую, а у светящейся руды на каждый пиксель выбирается одна грань ресемплированием по значимости: случайно берутся восемь светящихся граней, и одна из них остаётся с вероятностью, которая зависит от того, насколько она осветила бы этот пиксель. С повторным использованием резервуаров в выборе участвует и грань, выбранная в прошлом кадре. Потом к выбранной грани идёт один теневой луч. Светящиеся воксели становятся образцами света при синхронизации зеркала, по шестнадцать байт, в буфере на 2²⁰. Первый же полёт его заполнил: 997 740 из 1 048 576. На одном только уровне 417 388 светящихся граней, и их извлечение заняло 25,9 мс. Фаза 6 остановилась на трёх порогах: шлейфы, дальняя зона кадра и 3,0 мс. Поправка в 15:39 вставила ещё и фазу для четвёртой проблемы: из-за дальних кандидатов проход света стоил от 5,3 до 12,2 мс против планки в 6,0. Из этой фазы вышел ADR-0025: каждый кадр строится таблица чанков с излучателями не дальше четырёх чанков от глаза; вес — мощность, делённая на расстояние. Цену он формулирует двумя фразами: «Оценка смещена по построению. Излучатель за радиусом не даёт ничего». С таблицей пик полёта упал до 18 522 образцов, ни одного вытеснения, ни одного отказа.

Арифметику проверяет закрытый ящик со светящимся потолком, где освещённость пола можно посчитать вручную: 1,7408. Движок показывает 1,7388, на 0,11 процента меньше. С таблицей ближних излучателей вместо равномерного выбора он показывает 1,7914, на 2,9 процента больше, зато ошибка на пиксель падает с 1,14 до 0,40.

Потом фаза 12, моя, вечером 21-го. В логе осталось, что я сказал:

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

И короче: «f5 ничего не делает», «грубый теневой обход темнее, но заметной разницы в качестве нет» и «я должен видеть, какой сейчас режим, или хотя бы что он сменился». Ревью при закрытии отказалось закрывать план и добавило три фазы. У мерцания была простая причина, словами ревью: «смешивание никогда не опускается ниже ALPHA = 0.2, так что неподвижный пиксель вечно усредняет около девяти кадров оценки по одному образцу». Исправление дало свету от светящейся породы собственную историю, по умолчанию 32 кадра, а долю фонаря оставило на смешивании 0,2. Среднее изменение неподвижного пикселя за кадр упало с 0,57–1,04 до 0,15–0,18 по четырём конфигурациям. Итог ADR-0018: «Две истории, а не одна. Владелец видел, как статичный свет излучающих вокселей мерцает при неподвижной камере и общем коэффициенте смешивания 0,2». Теперь, когда режим меняют клавишей, он печатается на экране.

Утром 22-го я облетел его снова и доложил: «освещение в половинном, грубый теневой обход, повторное использование сэмплера, история правок, фонарь выключен, фильтр включён, пульсация руды выключена, история свечения 32 — всё нормально. нажатие f5 ничего не меняет». F5, которая переключает на градиентную историю, мне так ничего и не показала, и то, что эта история не сработала, приняли как есть. Эти настройки стали настройками по умолчанию: освещение в половинном разрешении, грубый теневой обход по блокам 2 × 2 × 2, повторное использование резервуаров. Грубый обход стал режимом по умолчанию только на той последней фазе, после того как я сказал, что не вижу заметной разницы в качестве.

Ракурс README на версии 0.32.0

Ограниченный уровень, сид 1, из вокселя (148, 280, 484), рыскание 135, тангаж −10, экспозиция 0,08, с настройками освещения по умолчанию и видом contour-sharp, 64 кадра, 1280 × 720 на RTX 3080.

Стена пещеры под налобным фонарём, по ней сверху вниз идёт жила тёплой жёлтой светящейся руды, ниже серые блоки породы, освещённые фонарём и слабо — рудой
Налобный фонарь и жила светящейся руды

Отрисовано одноразовым примером на основе заглавного кадра самого движка, потом удалённым; все флаги — из README.

Освещение и шумоподавление при двух разрешениях

Миллисекунды при 1920 × 1080 на RTX 3080, верхняя граница диапазонов, снятых на тихой машине на ограниченном уровне и в пещере с подгрузкой. Отражённый свет — на графике бюджетов в конце.

  • планка
  • измерено
Свет + временной проход, половинное разрешение 6 мс 2,95 мс
Один проход света, половинное разрешение 4 мс 2,81 мс
Один проход света, полное разрешение 4 мс 14,53 мс
Шумоподавление, половинное разрешение 3 мс 1,15 мс
Шумоподавление, полное разрешение 3 мс 5,27 мс

docs/nfr.md §2. Строки половинного разрешения — итоговые настройки по умолчанию с грубым теневым обходом; строки полного сняты с полным обходом. Собственный порог прохода света — 4,0 мс внутри планки освещения в 6,0.

Показать числа
планкаизмерено
Свет + временной проход, половинное разрешение 6 мс2,95 мс
Один проход света, половинное разрешение 4 мс2,81 мс
Один проход света, полное разрешение 4 мс14,53 мс
Шумоподавление, половинное разрешение 3 мс1,15 мс
Шумоподавление, полное разрешение 3 мс5,27 мс

Одна планка по-прежнему не выполнена: у извлечения образцов света при синхронизации p99 — 2,21 мс против 1,0, а худший кадр — 6,1. Я облетел ради неё мир с подгрузкой и написал «3 — всё хорошо», так что она записана как невыполнение, а рядом стоит моя оценка.

Промежуточный пояс

Шестой день закончился невыполненной планкой подгрузки и измеренной причиной: 41,8 процента синхронизации приходилось на копирование в буфер GPU. Это исправление уже однажды отвергли, как вариант Б в ADR-0017, так что план 0027 начался с ADR-0022, который это отклонение отменяет. Он берёт StagingBelt из самого wgpu: постоянно отображённые в память куски по 4 МиБ, и каждая загрузка — это запись в срез пояса и копирование из буфера в буфер в энкодере кадра. Про собственное кольцо, которое планировал шестой день, в плане сказано: «пояс — это и есть то кольцо, только куски возвращает сам wgpu». Строка итога ADR: «„Один memcpy в отображённую память“ — не бесплатно».

Фаза 2 остановилась 21-го в 14:25, потому что на сам пояс приходилось 28,9 процента синхронизации при пороге в пять. Я решил записать это как невыполнение и снять замер заново на тихой машине. На тихой машине медиана синхронизации в простом виде упала с 3,3–3,7 мс до 1,4–1,7, а Queue::write_buffer пропал из профиля, но всё обновление вместе с опросом по-прежнему занимало от 1,7 до 2,0 против планки в 1,0. В 19:11 я облетел его в 1440p: «полёт 1 прошёл нормально, но fps было около 30». Оверлей читал таймеры обратно на CPU и тем самым сериализовал CPU и GPU, так что тридцать кадров в секунду были не на совести пояса. План закрылся в 19:42 версией 0.26.0, с двумя записанными невыполнениями.

Половина кирпича для зеркала

Упакованный кирпич шестого дня вчетверо сократил данные на CPU, а зеркало на GPU не тронул. В ADR-0012 было три шага; последним стал план 0028, 22-го. Слот полного кирпича в зеркале теперь хранит те же упакованные байты, что и на CPU, в единицах по 64 байта: две единицы для кирпича из одного материала, три для двух, до восемнадцати для кирпича, где материалов больше шестнадцати. Слоты бывают десяти классов размера на страницах по 256 КиБ, и свободны только 0,10 процента единиц. Атрибут контура, который на шестой день стоил 1 168 байт на кирпич, теперь хранится только для поверхностных вокселей, с адресацией подсчётом битов: в среднем 197 байт на кирпич.

Фаза 3 остановилась в 10:35. Проход атрибутов на GPU показал p99 5,77 мс против 1,0, а кирпич на 99-м перцентиле ждал в очереди 7,6 секунды. В 10:59 я внёс поправку: убрать записи очистки, класть по четыре кирпича в рабочую группу и добавить порог в 250 мс на то, сколько кирпич ждёт. Итоговый проход показывает от 0,71 до 0,75 мс на p99, а кирпич ждёт 25 мс.

Зеркало на GPU до и после плана 0028

Мегабайты на машине разработки.

  • до
  • после
Пещера с подгрузкой, вид по умолчанию, живые данные 2 864 МБ 872 МБ
Ограниченный уровень, вид с контурами 680,5 МБ 157,9 МБ
Ограниченный уровень, простой вид 232,4 МБ 62,8 МБ

docs/plans/done/0028-size-classed-mirror-slots-and-surface-only-attributes.md и docs/nfr.md §3. Строка пещеры с подгрузкой снята на том радиусе полной детализации, на котором установилась каждая версия: 13 чанков до, 16 после.

Показать числа
допосле
Пещера с подгрузкой, вид по умолчанию, живые данные 2 864 МБ872 МБ
Ограниченный уровень, вид с контурами 680,5 МБ157,9 МБ
Ограниченный уровень, простой вид 232,4 МБ62,8 МБ

Важна здесь строка пещеры с подгрузкой. На шестой день действовало правило давления: когда зеркало заполнено больше чем на 90 процентов своих 3 ГБ, чанки полной детализации снимаются. Из-за него вид по умолчанию держал 12–13 чанков полной детализации, от 77 до 83 м, против «около 100 м» из дорожной карты, и файл бюджетов записал это как наблюдение. На 0.29.0 вид по умолчанию держит 16 чанков, 102 м, при 0,87 ГБ живых данных, 65 процентах бюджета. А синхронизация простого вида упала с 4,9 мс по медиане до 0,17, впервые ниже планки в 1,0. Синхронизация вида по умолчанию по-прежнему 3,1 мс, потому что маски поверхности считаются на потоке рендера, и теперь открытая часть планки подгрузки — именно она. Два моих полёта: «прогон 1 ощущается нормально» и «2 нормально».

Планка правки, третий заход

План 0031 шёл 21-го. Его первая фаза — профиль 100 выкапываний по стадиям: отсечение 34 процента, отрезки 27, запись слов 15. План стал строить слова формы прямо из строк-отрезков и считать строку каждого отрезка один раз на операцию, а не на каждый кирпич. Одна из его фаз сократила время на шесть–восемь процентов вместо ожидаемых 24. Два прогона на тихой машине прервали, потому что машина не была тихой, «kworker на 63 %, chromium на 78 %, скрипт omarchy на 100 %», и фаза около полутора часов ждала окна. Потом бенчмарк показал 1,00, 0,99 и 0,99 мс.

100 выкапываний по метру: пять планов, которые над этим работали

Миллисекунды CPU; на прогретой машине там, где план записал такой замер.

  • планка
  • измерено
План 0001, первый день 2 мс 4,92 мс
План 0003, строки-отрезки 2 мс 3,56 мс
План 0014, пул и строка капсулы 2 мс 3,03 мс
План 0021, упакованный кирпич 2 мс 1,49 мс
План 0031, третий заход 2 мс 0,99 мс

docs/nfr.md §4 и замеры при закрытии планов. На шестой день планку подняли до 2,2 мс после повторного замера на нагруженной машине; этим планом я вернул её на 2,0.

Показать числа
планкаизмерено
План 0001, первый день 2 мс4,92 мс
План 0003, строки-отрезки 2 мс3,56 мс
План 0014, пул и строка капсулы 2 мс3,03 мс
План 0021, упакованный кирпич 2 мс1,49 мс
План 0031, третий заход 2 мс0,99 мс

Бенчмарк воспроизведения упал со 103 мс до 61,3. В первый день планка выкапываний была тем бюджетом, в который движок не уложился в два с половиной раза; теперь она выполнена с двукратным запасом.

Отражённый свет

Непрямой свет пришёл с планом 0029 и ADR-0023 в виде объёма из 32 × 32 × 32 зондов освещённости с шагом 1,6 м, который сдвигается вместе с камерой. Каждый кадр один зонд из восьми, плюс каждый зонд, которого коснулась правка, пускает 64 луча полным обходом, 262 144 луча за кадр, и сохраняет то, что видит во всех направлениях, развёрнутым в карту света 6 × 6 и карту расстояний 14 × 14. Потом каждый пиксель собирает свет с зондов вокруг себя, а карты расстояний не дают зонду за стеной его освещать. Среди отвергнутых вариантов — все привычные названия: ReSTIR GI, voxel cone tracing, кэш освещённости на воксель, radiance cascades.

Этот план я смотрел чаще любого другого за эти дни, четыре сессии, и после каждой он менялся. Сессия 7, 22-го: «отражённый свет делает лучше, но бьёт по производительности»; «то же самое, выглядит лучше, но производительность плохая — у меня 10 fps», и это были пики синхронизации подгрузки; и «так гораздо лучше. но когда я выключаю свет, ещё какое-то время немного вижу его на стенах». С зондами через восемь вокселей: «ощущается неправильно — очень долго устаканивается». Сессия 8:

всё ещё есть задержка, и ощущается это очень неестественно, в реальности, если выключить яркий свет, я в первые секунды ничего бы не видел, а потом глаза постепенно привыкли бы к темноте, и я снова видел бы нормально

Эта фраза стала отдельным планом, 0040, адаптацией глаза, о ней ниже. А в этом плане из неё следовало одно: убрать задержку. ADR признаёт, что первая оценка была неверной: «Задержку нашёл владелец, и „полсекунды“ выше — неверная цифра для выключателя». Теперь изменение света открывает окно в 16 кадров, в котором обновляется один зонд из четырёх, а история не сохраняется вовсе. Сессия 9: «свет всё ещё долго гаснет», а пульсация руды «совсем плохая, вокруг сильное мерцание». Ещё через две фазы, в сессии 11 около десяти вечера: «почти мгновенно», «мгновенно», пульсация «нормально, без мерцания». План закрылся в 22:10 версией 0.31.0. В тестовой сцене после выключения фонаря его отражённый свет теперь падает ниже десяти процентов к 14-му кадру, а раньше — только к 58-му.

Коридор подземелья замка без отражённого света и с ним

Кадр замка dungeon, налобный фонарь включён, настройки освещения по умолчанию, 64 кадра в 1280 × 720 на RTX 3080; во втором кадре добавлен объём зондов.

Длинный каменный коридор с зарешёченными темницами по обе стороны, освещён только внутри круглого конуса налобного фонаря; всё вне его чёрное
Только прямой свет
Тот же коридор с отражённым светом: стены и решётки темниц вне конуса фонаря тускло видны в сером, у факелов тёплый оттенок
С объёмом зондов

Всё, что видно на втором кадре вне конуса и вдали от двух факелов, — отражённый свет.

Цена в точке появления в мире с подгрузкой: обновление и сбор вместе — от 1,63 до 1,66 мс против порога в 1,7, а всё освещение вместе с ними — 4,42–4,48 мс против планки в 6,0. На стороне AMD одно только обновление — 7,8 мс. Непрямой свет включается флагом, --indirect, а в моей доработке сказано, как я хочу его использовать: «оставить непрямой свет только для некоторых источников, вроде огня на палке или огня в подземелье, а для простых источников он должен быть выключен».

Апскейл, дважды

Апскейлер плана 0030 и ADR-0024 — собственный: лучи смещаются по 16-фазной последовательности Халтона, кадр рисуется во внутреннем масштабе 1×, 1,5× или 2×, а отдельный проход перепроецирует историю по позиции попадания и повышает резкость. В масштабе 1,5 кадр занимает от 2,73 до 2,78 мс, а в масштабе 1 — от 5,29 до 5,55, и апскейл вместе с повышением резкости и композитингом — 0,76 мс против планки в 1,5.

Моя первая проверка в окне, сессия 7: «всё очень размыто, особенно с налобным фонарём», «выглядит чуть лучше, всё ещё размыто», «выглядит очень размыто даже без контуров». Вердикт: переделать до закрытия. Переделка сузила ядро, стала брать историю фильтром Catmull-Rom, смешивать по близости и обрезать историю при смене света. Посреди неё, в 14:19, я попросил линию остановиться с контрольной точкой, потому что ноутбук работал от батареи. Сессия 8: «на прогоне 4 есть шлейфы, но уже не так сильно. может, это нормально как часть визуального стиля», «5 так себе», «6 почти нормально» (6 — кадр в родном разрешении), «7 так себе, слишком много движения теней». Так что по умолчанию остался родной кадр, апскейлер вышел включаемым по флагу, версией 0.28.0, а порт FSR 2 стал планом 0041.

План 0041 перенёс FSR 2.2.1 на WGSL, девять проходов. Он не влит: на отсечке его блок закрытия написан, а ревью ещё впереди. Фаза 7 была моей, 27-го, с 17:25 до 17:36:

fsr 2 вроде чуть мягче, но от нашего не сильно отличается

и «на fsr, наверное, слишком много деталей», и «я за наш 1,5 — с fsr всё равно разница небольшая». Ещё: «с нашим апскейлером мерцание, когда выключаю фонарь, похоже, минимальное». На ветке режимом по умолчанию становится наш апскейлер в масштабе 1,5, со смещением лучей.

Кадр с каждым апскейлером, на ветке плана 0041

Миллисекунды GPU на RTX 3080; оба апскейлера во внутреннем масштабе 1,5. Кадр на ветке тяжелее, чем в плане 0030, так что сравнивать стоит только внутри графика.

Апскейлер выключен 6,88 мс
FSR 2.2.1 на WGSL 5,81 мс
Наш апскейлер 4,51 мс

Верхняя граница каждого диапазона в plan-0041:docs/nfr.md. Девять проходов FSR сами по себе — от 1,89 до 1,91 мс против порога в 1,2, невыполнение; на стороне AMD — 10 мс.

Показать числа
мс
Апскейлер выключен 6,88 мс
FSR 2.2.1 на WGSL 5,81 мс
Наш апскейлер 4,51 мс

Здесь FSR 2 стоит на 1,3 мс больше нашего, а большой разницы я не увидел.

Замок

Мне хотелось показать движок коллегам с работы. План 0042 набросали 22-го в 18:13, одобрили в 18:15, а закрылся он в 21:30 версией 0.30.0, без единой остановки фазы. Он написал для движка запись файлов .vox и генератор замка только на целочисленной арифметике: стены, четыре башни, надвратная башня, донжон-капелла с нефом, окном-розой и шпилем, винтовая лестница и подземелье с темницами и склепом. Это 27 тайлов, из которых оставлен 21; при генерации они штампуются в вырезанный зал 80 × 80 × 48 м через тот же путь префабов, что и своды шестого дня. Вектор детерминизма и эталон ворот проверяют его, как и всё остальное.

Мой просмотр в сессии 10 — ворота, неф, окно и подземелье: «замок выглядит потрясающе».

Замок на версии 0.32.0

Кадры замка из стенда, отрисованы headless в 1280 × 720 на RTX 3080 с настройками освещения по умолчанию и видом contour-sharp.

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

При экспозиции по умолчанию неф под одним налобным фонарём почти чёрный; что там есть, показывают объём зондов и повышенная экспозиция.

Замок не маленький. Его префабы занимают 343,6 МБ, из них около 92 процентов — воздух, файл .vox весит 52,6 МБ, пик процесса выше примерно на 0,3 ГБ, а чанк генерируется за 424–428 мкс против 186–189 для одного зала. Первый кадр появляется через 1,1–2,6 секунды, ниже планки запуска в три. Кадр ворот рисуется за 7,2–8,0 мс на RTX и за 51 мс на стороне AMD. Последний коммит этого отрезка, 27-го в 18:52, дал замку постоянное место: assets/architecture/, для переиспользуемых зданий; файлы .vox git игнорирует, и они пересоздаются командой из README.

Контуры на расстоянии вытянутой руки

В сессии 7 22-го, разглядывая апскейлер, я сказал про родной кадр, что он «выглядит несколько лучше, без контуров выглядит совершенно нормально». Контуры были режимом по умолчанию с пятого дня, а вблизи выглядели мягкими. План 0039 набросали в тот же день, чтобы это измерить. Он построил сцену для замера резкости — вырезанную стену на расстоянии вытянутой руки, с полосой мелких деталей, полосой средних, размытием края и двумя страховочными проверками — и пять кандидатов: взвешенную подгонку плоскости по 27 соседям, смещение с сохранением объёма, смесь с нормалями граней, более мелкую октаву шума породы и коэффициент детализации фонаря, применённый после освещения.

Первая фаза показала, что плоскость — не главная проблема. Разрыв в полосе мелких деталей между режимом граней и контурами на 74,6 процента объяснялся освещением в половинном разрешении. ADR-0011 получил строку итога: «Мягкость шла от пути освещения, а не от подгонки». Размытие края — на сколько пикселей размазан острый край стены — показало то же: 0,80 пикселя в режиме граней, то есть в простом виде, против 13,6 с контурами. Кандидат со смесью нормалей граней не прошёл страховочную проверку на полостях, 12,1° и 21,9°, и его отбросили.

План стоял с 22-го по 27-е вместе со всем остальным. Утром 27-го я сравнил пресеты и выбрал: «contour-sharp становится режимом по умолчанию», то есть взвешенная подгонка плюс коэффициент детализации после освещения, потому что «contour-grain поначалу выглядел естественнее всех, но кладёт на породу слишком много тёмных крапинок, хуже всего там, где фонарь скользит по стене (жёсткие чёрные пятна)». Перепроверяя с отражённым светом, я нашёл ещё одно: «contour sharp, похоже, наш выбор, но на второй сцене я видел артефакты от острых углов вокселей на некоторых гладких стенах», и это ушло в доработки. Проход исправлений нашёл, что коэффициент детализации масштабировал и отражённый свет, так что теперь композитинг держит прямой и непрямой свет раздельно. Цена — в пределах процента в обе стороны на обоих GPU, а байты зеркала не изменились. Версия 0.32.0, 27-го в 17:27.

Стена теста резкости в четырёх видах

Эталоны apps/flythrough/tests/sharpness.rs: вырезанная стена на расстоянии вытянутой руки под налобным фонарём, 320 × 180 на программном драйвере lavapipe, увеличены вдвое.

Каменная стена вблизи под ярким фонарём, нарисованная кубиками с жёсткими краями и чёрными щелями между ними
Нормали граней
Та же стена с контурными плоскостями: кубики сглажены в крупные мягкие поверхности
Контуры, прежний режим по умолчанию
Та же стена в contour-sharp: гладкие поверхности с чуть более чёткими краями, чем у контуров
Contour-sharp, теперь режим по умолчанию
Та же стена в contour-grain: мелкая фактура породы поверх поверхностей, много мелких тёмных крапинок и чёрных штрихов
Contour-grain, с тёмными крапинками

Фонарь так близко, что засвечивает стену; тест меряет полосы и края, а не приятную экспозицию.

Тесты и Windows

Вечером 22-го я попросил ревью кода всего репозитория и сказал про тесты: «ощущение, что их стало гораздо больше, чем было, и гоняются они бесконечно долго, надо оптимизировать прогоны». План 0043 разбил их на три уровня: quick — 470 тестов за 45,4 с без блокировки GPU; gpu — ещё 124, 201,5 с; full — ещё 5, 327,1 с. Задача CI на Linux сократилась с 981 с до 827.

Тем временем задача на Windows падала на каждом прогоне с тех пор, как план 0028 переписал шейдер атрибутов, и всегда на одном тесте: headless-полёт стенда, который должен завершиться за 120 секунд. Подозрение пало на FXC, старый компилятор шейдеров DirectX, который компилировал новый шейдер атрибутов. План 0046 встроил статический компилятор DXC, а ADR-0035 запрещает FXC навсегда, но с оговоркой: «То, что причина в FXC, — вывод из логов CI; локально FXC не запускали».

Холодное создание одиннадцати пайплайнов на WARP, программном DirectX раннера CI

Миллисекунды, прогон CI 39843578678.

  • FXC
  • статический DXC
Все одиннадцать пайплайнов 76 623,6 мс 2 507,9 мс
Один проход атрибутов 42 412,2 мс 596 мс

docs/plans/done/0046-dx12-shaders-through-bundled-static-dxc.md. Для сравнения, на Linux: 1,4 с на RTX, 0,46 с на RADV, 0,30 с на программном драйвере lavapipe.

Показать числа
FXCстатический DXC
Все одиннадцать пайплайнов 76 623,6 мс2 507,9 мс
Один проход атрибутов 42 412,2 мс596 мс

В тридцать раз быстрее, а тест в виде по умолчанию всё равно падал: 121,055 с при лимите 120: время уходило не на компиляцию прохода атрибутов, а на его исполнение в WARP. Исправление CI в 20:58 уже перевело этот тест на Windows на --look plain, и с ним задача зелёная; этот обходной путь остаётся. Версия 0.31.1 в 22:30 22-го стала последним коммитом перед перерывом.

27-е

Работа возобновилась в 07:03, рядом с планом 0039 были открыты три линии, и к вечеру каждой из них понадобился я.

Адаптация глаза, план 0040. План родился из фразы сессии 8 о том, что, когда гаснет сильный свет, сначала ничего не видишь. ADR-0033 предложен; проход экспозиции стоит 0,07 мс. К 10:28 были готовы четыре фазы, а моя сессия в 17:37 нашла следующую проблему: «самый медленный — лучший, но мерцание придаёт ему странный вид. когда я выключаю фонарь и сцена становится светлее, кажется, будто на стенах одна за другой загораются несколько ламп в течение 10–15 секунд, и это выглядит супер странно и неестественно». Поправка задаёт новые константы времени адаптации, но в код ещё не вошла.

Витринная каверна, план 0038. Поводом стала моя фраза 22-го: «заглавная картинка убогая, надо сделать что-то поинтереснее и без налобного фонаря, в большой пещере». План строит большую каверну с четырьмя рудами и фиксированными заглавными кадрами, 99 326 образцов света, кадры от 10,7 до 13,5 мс на RTX и от 76 до 100 на стороне AMD. Пять фаз готовы; шестая — мой просмотр.

FSR 2, план 0041, о нём выше, ждёт ревью при закрытии.

Последнее изменение харнесса в тот день, в 18:11: dev-линии и ревью при закрытии идут на Opus, рутинные задачи, которые не трогают код, — на Sonnet, и у каждой линии есть бюджет контекста.

Бюджеты на отсечке

Планки кадра и CPU против замеров, 27 октября

Миллисекунды на машине разработки на версии 0.32.0; верхняя граница диапазона там, где файл его даёт.

  • планка
  • измерено
Первичные лучи (проход попаданий), пещера с подгрузкой 3 мс 1,78 мс
Освещение, половинное разрешение, с отражённым светом 6 мс 4,48 мс
Шумоподавление, половинное разрешение 3 мс 1,15 мс
Апскейл и постобработка, апскейлер по флагу, 1,5 1,5 мс 0,77 мс
Синхронизация подгрузки, простой вид, p50 1 мс 0,17 мс
Синхронизация подгрузки, вид по умолчанию, p50 1 мс 3,09 мс
Извлечение образцов света, p99 1 мс 2,21 мс
Проход атрибутов, поток рендера, p99 4 мс 3,73 мс
100 выкапываний по 1 м 2 мс 1 мс

docs/nfr.md §2 и §4 на коммите 04d3bea. Планки таблицы кадра за эти дни не менялись.

Показать числа
планкаизмерено
Первичные лучи (проход попаданий), пещера с подгрузкой 3 мс1,78 мс
Освещение, половинное разрешение, с отражённым светом 6 мс4,48 мс
Шумоподавление, половинное разрешение 3 мс1,15 мс
Апскейл и постобработка, апскейлер по флагу, 1,5 1,5 мс0,77 мс
Синхронизация подгрузки, простой вид, p50 1 мс0,17 мс
Синхронизация подгрузки, вид по умолчанию, p50 1 мс3,09 мс
Извлечение образцов света, p99 1 мс2,21 мс
Проход атрибутов, поток рендера, p99 4 мс3,73 мс
100 выкапываний по 1 м 2 мс1 мс

На шестой день планкам освещения, шумоподавления и апскейла не с чем было сравниваться. Теперь замер есть у каждой строки таблицы кадра, кроме запаса, а все невыполнения — на пути данных с CPU в зеркало: синхронизация вида по умолчанию и извлечение света, а через них и планка подгрузки. Планки памяти выполнены все: 0,87 ГБ из бюджета мира в 3 ГБ при 16 чанках, ОЗУ сильно ниже 4 ГБ, а буферы вывода кадра вместе с зондами — 254 МБ против 1,5 ГБ. Код звука и коллизий за эти дни не менялся, так что эти разделы не сдвинулись, и два невыполнения микшера с шестого дня остаются.

Что дальше и что открыто

Следующая веха, M10, — под открытым небом; её набросали и одобрили утром 22-го: план 0032, солнце и небо, со своей тестовой сценой и лесом при низком солнце, о котором я попросил; 0033, дальний план до километра; 0034, жидкости; 0035, воздушная перспектива и облака; 0036, погода, с правилом ADR-0031 «в пещере всегда сухо»; 0037, ночное небо. ADR-0027 о тени от солнца формулирует приоритет одной фразой: «Лучше короткое тёмное мерцание, чем солнечный свет, протёкший в пещеру». Ни один из шести не начат. После них — два плана о здоровье кода по итогам ревью 22-го: шесть подтверждённых дефектов, среди них тест, который утверждает, что HUD не выделяет память, хотя он её выделяет, и разбор структуры разросшихся файлов: main.rs стенда на 5 771 строку и шейдер прохода лучей на 1 383.

Кроме того, открыто: три линии 27-го; синхронизация вида по умолчанию; извлечение света; острые углы на гладких стенах; материалы замка, у которых нет акустических значений для звука, так что под --audio камень замка не отражает; и DualSense на диване, до которого я так и не добрался.

Как это было организовано

Харнесс тот же, и с 22-го он записан: .claude/skills/orchestrate/ описывает одну оркестрирующую сессию, которая ведёт несколько линий планов одновременно, и каждая линия — dev-агент со своим git worktree. Линии по очереди занимают GPU через одну эксклюзивную блокировку, каждая команда идёт под лимитом памяти в 6 ГБ, линия останавливается на каждой фазе с пометкой human и ждёт меня у скрипта запуска, который открывает сборку в окне с нужными флагами, а каждое закрытие проверяет свежий агент-архитектор.