Секреты и трюки старых консолей и компьютеров
GameDev.ru / Сообщества / old_tricks / Статьи / Графическая архитектура Nintendo 64

Графическая архитектура Nintendo 64

Автор:

Nintendo 64

Nintendo 64 вышел в 1996-ом году и стал конкурентом вышедшей двумя годами ранее Playstation 1.
Центральным процессором в системе был тоже MIPS, но уже на базе 64-битного R4300i на 94МГц. Шина адреса, впрочем была урезана до 32 бит, что логично, ибо 64-битные целочисленные операнды вообще практически не использовались. Повышенная битность тут была больше маркетинговой, чем реальной небходимостью.
В качестве первого сопроцессора у него стоял так же как и в PS1 контроллер виртуальной памяти, а вот вторым был сопроцессор с плавающей точкой (которого у PS1 просто не было).
Встроенной в консоль основной RAM было 4Мб причём она могла расширяться до 8Мб посредством слота расширения. Отличительной особенностью было то, что байт в этой памяти был 9-битным, правда это мог увидеть и использовать только чип растеризации (RDP), который использовал эти биты для стенсиля (см. ниже). Остальные компоненты системы читали и писали байты только как 8-битовые значения. Еще одна особенность - встроенные 4Мб памяти физически находились на двух разных микросхемах по 2Мб каждая и тот же растеризатор мог работать с ними в параллельном режиме, поэтому, к примеру, официально рекомендовалось располагать фреймбуфер в нижних 2Мб, а Z-буфер - в верхних.
Так что в целом и по битности ЦП и по его частоте и по наличию плавающей точки Nintendo 64 была посовременнее PS1, временная фора свою роль сыграла.
Как мы знаем в N64 Nintendo сделала тогда уже непопулярный шаг и в качестве накопителя информации выбрала не оптические диски, а картриджи.
Неожиданным лично для меня оказался тот факт, что в глубоком отличии от консолей предыдущих поколений ROM картриджа больше не мапилось в основное адресное пространство процессора вообще никак.
ROM картриджа стало в N64 ничем иным, как внешним хранилищем к которому обращение идёт через DMA-каналы. Причина этого кроется видимо в крутом повышении частот доступа к основной RAM при которых дешевая ROM картриджей стала сильно отставать - до сотни раз (5 Мб/сек чтения типичного медленного ROM против ~500 у RAM).
В итоге картридж из полноценного ROM стал не более чем внешним накопителем - с быстрым случайным доступом, конечно (и в целом быстрым), но всё же просто внешним накопителем. Так, при старте консоли в RAM копируется первый мегабайт картриджа и в него передаётся управление.

RCP

Интересно, что в Nintendo 64 центральную роль в системе играет видеочип - причём сам он по себе является многокомпонентной величиной.
Для того чтобы сделать унифицированную RAM и не разделять банки графической и основной ОЗУ в итоге было сделано так, что ЦП обращался к RAM через видеочип.
И вообще все пути-дорожки сходились именно на видеочипе - как общение с периферией, так и общение хоть с памятью хоть с картриджем как видео, как геймпада, как и звука - всё шло через видеочип.
Он получается становился каким то южным мостом в одном флаконе и точкой, где встречались все компоненты системы.
Весь видеочип в целом назывался RCP (Reality CoProcessor), при этом его можно было поделить на несколько больших и слабо зависимых компонент:
- VI (Video Interface) - просто выводит какой либо прямоугольный блок пикселей из любого места в RAM на видеовыход системы, допустимые битности цвета - 16 бит (1:5:5:5) и 32 бита (RGBA).
- RDP (Reality Display Processor) - растеризатор от SGI. Рисовал графические примитивы вполне в духе трёхмерных видеокарт своего времени (без T&L) в целочисленных координатах screenspace выполняя некий буфер команд. Для текстурирования у этого чипа было выделено 4Кб специализированный памяти под текстуры (TMEM), которые во многом были узким местом всей консоли - даже на фоне PS1 кадры из лучших игр N64 выглядят мыльновато.
- RSP (Reality Signal Processor) - еще один базированный на MIPS R4200 процессор на 62МГц, уже без сопроцессора с плавающей запятой, зато с неким SIMD-сопроцессором опять таки для решения задач T&L с регистрами с фиксированной точкой, формат и состав дополнительных команд которого, однако нигде я так и не смог нарыть толком.
RSP тоже обладал двумя собственными специализированными банками памяти - IMEM и DMEM - каждый опять же по 4Кб. В первом хранились инструкции для RSP, а во втором - данные. Что-то там было такое опять таки в сопроцессоре RSP, что это было два разных банка памяти.
Важно, что с основной RAM RSP мог общаться только посредством своего DMA-канала, всю обработку он должен был проводить внутри этих крошечных 2x4Кб памяти. Заниматься он должен был задачами T&L и процессинга звука. Судя по всему - во втором качестве очень редко, т.к. с этим мог справится и ЦП.
А вот с графикой всё и так было в притык. Nintendo со старта консоли выпустила несколько "прошивок" (массивов "микрокодов"), которые ЦП должен был загружать в IMEM, чтобы использовать мощь RSP.
Первая из прошивок называлась Fast3D - это основная к использованию вещь, с большей частью и как правило точных функций. Но надо заметить, что координаты все тут были в screen-space и либо целочисленные, либо с фиксированной точкой после запятой. Так, например, ни координаты треугольника ни их разности по модулю не должны были превышать 32767, такое могло бы привести к глюкам обработки точки.
Вторая прошивка называлась Turbo3D - в ней была снижена точность обработки вершин и вырезаны многие фичи: обрезка по фруструму, освещение, перспективная коррекция текстур, стек матриц.
Третий микрокод - Line3D умел почти всё то же что и Fast3D, но рисовал не треугольники, а линии для wireframe.
Кроме того каждый микрокод был представлен еще в трёх вариантах того как он выдавал результирующие команды в растеризатор RDP. Первый - через собственный крохотный FIFO-буфер в собственной DMEM. Второй - через FIFO-буфер в RAM (имеет смысл размер от 1Кб). Третий вариант заливает все команды в один большой буфер в RAM не передавая его в RDP, чтобы это мог сделать ЦП по своему усмотрению.

Использовать RSP предполагалось по следующей схеме:
а) ЦП создаёт в памяти списки команд - это потенциально древовидные последовательности инструкций для RSP (до 10 уровней вложенности, т.к. одна из инструкций переходит к другому списку инструкций, а по его завершению возвращается обратно, стек возвратов при этом имеет глубину 10).
б) ЦП загружает в RSP микрокод под формат списка и передаёт указатель на первую инструкцию какого либо списка в RSP для начала обработки. В сущности списки команд содержат команды специфичные для того или иного микрокода - он их парсит и обрабатывает по заданному в IMEM алгоритму.
в) RSP начинает обрабатывать список команд - выполнять T&L, обрезку выпавших за камеру полигонов и т.п., формируя в итоге список команд для RDP и передаёт его собственно в RDP через порты ввода-вывода или DMA.
В силу ограниченности размеров IMEM/DMEM следует упомянуть как осуществлялась обработка и отрисовка вершин. Стандартные микрокоды размещали в DMEM массив из 16 вершин с рассчитаными атрибутами - команды в RSP рисующие треугольники содержали лишь индексы этих обработанных вершин (0-15). Загрузка в этот массив вершин осуществлялась отдельной командой загрузки из RAM вершин, которая тут же и рассчитывала трансформации им и освещение. Таким образом не могло идти подряд много команд отрисовки треугольников, если у всех них было больше 16 разных вершин - нужно было чередовать команды загрузки вершин и рисования треугольников порциями по не более чем 16 вершин между загрузками.

Таким образом в N64 мы можем говорить уже не только о полноценном, вынесенном из основного ЦП блока рассчёта T&L, но и о его изначальной полноценной программируемости - банк данных IMEM заполнялся в полной концептуальной аналогии с вершинными шейдерами.
Однако тут происходит нечто странное для меня - в первые годы после выхода консоли Nintendo никому не раскрывало формата команд RSP и его внутреннего устройства - предполагалось, что разработчики будут пользоваться только предложенными производителем банками IMEM распространяемыми с SDK в виде массивов байтов.
Ни инструментария, ни описания - ничего не было, поэтому разработчики так и поступали. При этом 4Кб под IMEM и 4Кб под DMEM действительно наступали на горло - так, например, банки Fast3D и Turbo3D не умели рисовать линии, отчего банк Line3D распространялся рядом с ними же.
Так что долгое время всё ограничивалось только тем, что Nintendo выпускало патчи к имеющемуся микрокоду и дополняло набор из трёх микрокодов какими то новыми - например был выпущен микрокод S2DEX для отрисовки двумерной графики/спрайтов.
И только под закат уже консоли Nintendo поспособствовала и несколько разработчиков внедрили свои коды в RSP, заточенные под их движки и форматы вершин - и это (по их словам) действительно сыграло в жирный плюс.

Списки команд подаваемые в RSP формировались в официальном API как правило как просто массив из макро-определений. Они были очень похожи на команды отдаваемые в RDP уже под растеризацию, собственно нередко просто повторяли их для простоты системы. Это были как правило 8-байтовые (64-битные) блоки байт, первый хранил код инструкции, а остальные - параметры (если они были, бывало и без параметров). Если параметров было много, блок мог увеличиваться с гранулярностью в те же 8 байт.

+ Для справки можно рассмотреть что же скармливалось в конце концов собственно растеризатору - RDP...

Забавным еще показалось отношение RDP к текстурированию - одним из глобальных стейтов RDP является "режим рендера". Режимы заливки и копирования предназначены для 2D-графики (при этом всё-равно в качестве источника битмапов выступает TMEM), а вот полноценные 3D-треугольники требуют уже режимов или 1-cycle или 2-cycle. В 1-cycle обрабатывается 1 тексель на пиксель - в качестве источника цвета может быть только одна текстурная выборка. В режиме 2-cycle же может производится до двух текстурных выборок на пиксель. Забавно, что билинейная фильтрация текстур тоже абсолютно попадает под это определение - выборка из двух слоёв разных мипмапов требует режима 2-cycle. Соответственно режим мультитекстурирования (точнее - битекстурирования) уже несовмпестим с мипмапами, структурно это в N64 одна и та же фича двойной текстурной выборки.

Операционная система

Много внимания в официальной документации Nintendo уделяется операционной системе - так что нельзя не сказать пару слов о ней.
Как они сами пишут "ОС получилась столь маленькая, что мы даже не дали ей названия". Вообще она распространяется не как обычная ОС, а как просто набор статических библиотек на C/C++ и полностью попадает на картридж.
Поэтому линкер вполне себе выкидывает неиспользуемые куски "оси" по факту компиляции. Ось даже не предоставляет менеджеров памяти - предполагается что игры работают в Kernel Mode и имеют прямой доступ в память.
Тем не менее ОС предполагает и большую ставку делает на вытесняющую многозадачность. При этом единственный механизм синхронизации - очереди сообщений, приписанные к каждому потоку в системе. Никаких семафоров или критических секций - потоки просто футболят друг другу сообщения, засыпая когда их не остаётся и просыпаясь, когда другой поток или ОС подкидывает новых.
Должен быть, к примеру, самый неприоритетный "idle thread", который дёргается когда перестают выполнятся любые другие потоки. Поэтому много вещей в API сделано в асинхронном стиле, документация прямо настаивает на том, чтобы приложение состояло из многих потоков, нагружающих друг друга сообщениями, чтобы возникало минимум простоев графического конвеера и тому подобное.
Вообще по официальной документации видно, что создатели консоли уже бережно ограждают разработчиков от необходимости лазить грязными пальцами в порты ввода-вывода - API внушительное, покрывает все аспекты и прикрывает аппаратную начинку достаточно плотно.

Итоги

Подводя итоги - судя по всему Nintendo 64 мог бы заметно обогнать Playstation 1 по перфомансу и качеству картинки, если бы не родовые недостатки с форматом накопителя (максимум 64 Мб картриджа), зажатостью документации на RSP и всего 4Кб текстурной памяти. Это всё многое смазало, в прямом и переносном смыслах. Тем не менее в своём поколении консоль была вполне себе конкурентноспособна и подарила игрокам много замечательных игр.

Изображение

22 ноября 2017

#5generation, #64bit

2001—2017 © GameDev.ru — Разработка игр