В продолжение темы, представляем вашему вниманию перевод оригинала статьи от Бена Картера.
Ссылки на видео по этой статье:
У меня наконец-то появились результаты работы над проектом, которым я занимался в свободное время примерно около года.
Его идея возникла, когда я пытался придумать интересный проект для изучения Verilog и проектирования FPGA. Мне пришла в голову мысль о создании простого трассировщика лучей (частично я вдохновлялся успехами моего до ужаса умного друга, создавшего собственный GPU). Немного позже (наверно, потому, что мой мозг ненавидит меня и наслаждается придумыванием глупых заданий) всё это превратилось в вопрос: «а не будет ли интересно заставить SNES выполнять рейтрейсинг?». Так родилась идея чипа SuperRT.
Я хотел попробовать создать нечто, напоминающее чип Super FX, используемый в таких играх, как Star Fox. SNES в них выполняет игровую логику и передаёт описание сцены чипу в картридже, который занимается генерированием графики. Я намеренно ограничил себя использованием в конструкции единого самодельного чипа, а не ядра ARM на плате DE10 или любых других внешних вычислительных ресурсов.
Окончательные результаты выглядят примерно так:
Приношу извинения за плохое качество скриншотов, по какой-то причине моя плата захвата обеспечивает ужасные результаты при захвате сигнала со SNES, поэтому мне пришлось воспользоваться старым добрым способом «фотографируем экран с выключенным светом».
С показанной здесь Super Nintendo (строго говоря, это Super Famicom) снята крышка, чтобы было место для подключения проводов, но во всём остальном она совершенно не модифицирована. К ней подключена печатная плата с копией ужасной игры Pachinko, которую я приобрёл за 100 йен в местном секонд-хенде. ROM игры извлечён и заменён на кабельную муфту. Далее она проходит через набор схем сдвига уровня для преобразования 5 вольт SNES в 3,3 В, а затем подключается к плату разработки DE10-Nano FPGA с Cyclone V FPGA. Платы схем сдвига уровня совершенно ужасны, а их сборка превратилась в кошмар из-за обязательных интегральных схем, которые продаются только в корпусах с поверхностным монтажом. Однако со своей работой они справляются.
Чип SuperRT создаёт сцену при помощи специализированного языка команд, исполняемых одним из трёх блоков паралелльного выполнения кода чипа (по сути, это специализированные процессоры CISC) для расчётов тестов пересечения лучей. Описание сцены позволяет создавать объекты при помощи подмножества операций CSG: в качестве базовых строительных блоков используются сферы и плоскости, а при помощи операций OR, AND и вычитания они применяются для построения нужной геометрии. AABB тоже поддерживаются и в основном используются для тестов усечения (при желании их тоже можно рендерить, но они обладают более низкой точностью позиционирования по сравнению с другими примитивами, поэтому не особо полезны, за исключением задач отладки).
Рендерер испускает до четырёх лучей на экранный пиксель, вычисляя прямые тени от направленного источника освещения и одного зеркального отражения. Каждая из поверхностей имеет цвет рассеянного освещения и свойство отражающей способности; к ним можно применять модификаторы на основании результатов CSG или специализированных функций. Это используется для генерирования паттерна шахматной доски на полу.
Цвет луча для каждого пикселя вычисляется «движком лучей», обрабатывающим весь цикл жизни луча; он использует модуль «движка исполнения» для выполнения управляющей программы, которая описывает сцену столько раз, сколько необходимо для вычисления результатов луча. Сама управляющая программа загружается со SNES и хранится в локальном буфере ОЗУ на 4 КБ — анимация реализуется записью в этот буфер модифицированных команд. Дизассемблированный буфер команд выглядит следующим образом:
0000 Start
0001 Plane 0, -1, 0, Dist=-2
0002 SphereSub OH 2, 1, 5, Rad=5
0003 SphereSub OH 4, 1, 4, Rad=4
0004 SphereSub OH 5, 1, 9, Rad=9
0005 SphereSub OH 2, 1, 2, Rad=2
0006 SphereSub OH -0.5, 1, 2, Rad=2
0007 RegisterHitNoReset 0, 248, 0, Reflectiveness=0
0008 Checkerboard ORH 48, 152, 48, Reflectiveness=0
0009 ResetHitState
0010 Plane 0, -1, 0, Dist=-2.150146
0011 RegisterHit 0, 0, 248, Reflectiveness=153
0012 AABB 4, -2.5, 11, 8, 3.5, 13
0013 ResetHitStateAndJump NH 44
0014 Origin 6, 2, 12
0015 Plane -0.2929688, 0, -0.9570313, Dist=0.2497559
0016 PlaneAnd OH 0.2919922, 0, 0.9560547, Dist=0.25
0017 PlaneAnd OH 0, 1, 0, Dist=1
0018 PlaneAnd OH 0, -1, 0, Dist=4
0019 PlaneAnd OH -0.9570313, 0, 0.2919922, Dist=-1
0020 PlaneAnd OH 0.9560547, 0, -0.2929688, Dist=1.499756
0021 RegisterHit 248, 0, 0, Reflectiveness=0
Каждый движок исполнения — это процессорный модуль с 14-тактным конвейером, и обычно за такт завершается выполнение одной команды, поэтому каждый модуль исполнения может вычислять примерно по 50 миллионов пересечений сфер, плоскостей или AABB. Исключением является то, что операциям ветвления нужно очищать весь конвейер, а следовательно, они тратят 16 тактов (14 тактов на очистку конвейера + 2 тактов задержки на получение команды). Чтобы по возможности избегать этого, используется система прогнозирования ветвления — к счастью, часто пространственная связность соседних лучей приводит к высокому уровню совпадения прогнозов.
Пересечения в движке исполнения вычисляются двумя конвейерами — один обрабатывает AABB, другой — сферы и плоскости. Система в целом работает исключительно с 32-битной целочисленной математикой в формате фиксированной запятой 18.14; если известно, что значения находятся в интервале ±1, то используется 16-битный (2.14) формат, а конвейер вычисления пересечений сфер/плоскостей имеет два дополнительных специализированных математических блока, вычисляющих операции обратных значений и квадратного корня.
При рендеринге кадра модуль преобразования PPU превращает буфер кадра в формат, который при помощи DMA можно передать напрямую во VRAM консоли SNES для отображения, ужав его до 256 цветов и заменив его на битовые плоскости тайлов символов. Экран имеет разрешение 200x160, то есть полный кадр занимает ровно 32000 байт данных изображений, которые из-за ограничений пропускной способности передаются во VRAM как два фрагмента по 16000 байт в следующих друг за другом кадрах. Следовательно, полное изображение можно обновлять только раз в два кадра, что ограничивает максимальную частоту кадров 30FPS. Однако тестовая сцена работает с частотой ближе к 20FPS (в основном из-за «узких мест» на стороне логики SNES).
Большое спасибо участникам этого треда на SNESdev за множество полезных идей о DMA полноэкранного чипа расширения, благодаря которым я придумал описанное мной решение.
В этом чипе также реализовано множество других базовых функций — есть интерфейс с шиной картриджа SNES, а также небольшой ROM для программ, содержащий 32 КБ кода для SNES (он ограничен тем, что плата интерфейса пока подключена только к линиям адресной шины A консоли SNES, а поэтому доступное адресное пространство составляет всего 64 КБ, из которых 32 КБ используются для регистров ввода-вывода с отображением в память, применяемых для связи с чипом SuperRT). Также присутствует блок ускорения операций умножения, позволяющий SNES быстро выполнять операции умножения 16x16 бит.
Для отладки я использовал интерфейс HDMI платы DE10, выводя данные на второй монитор, а также геймпад Megadrive, подключённый к контактам GPIO для управления системой отладки. Однако из-за ограниченных ресурсов при включении всех трёх ядер движка лучей отладку приходится отключать.
Вот краткий обзор системы, в будущем я планирую опубликовать новые статьи с более подробным описанием работы отдельных компонентов. Однако если у вас есть вопросы или мысли, то свяжитесь со мной, и я постараюсь ответить!
Огромное спасибо Мэтту, Джеймин, Рику и всем тем, кто помогал советами, вдохновением и поддержкой!
На правах рекламы
Надёжный сервер в аренду и правильный выбор тарифного плана позволят меньше отвлекаться на неприятные уведомления мониторинга — всё будет работать без сбоев и с очень высоким uptime!
stasrusnak
Вот это я понимаю, пора делать ремейки на хиты прошлых лет!