Секреты и трюки старых консолей и компьютеров
GameDev.ru / Сообщества / old_tricks / Статьи / Графическая архитектура SNES (+сравнение с Mega Drive)

Графическая архитектура SNES (+сравнение с Mega Drive)

Автор:

CPU

ЦП у SNES был Ricoh 5A22, который является немного переработанным клоном WDC 65816, который в свою очередь является 16-битным потомком 8-битного MOS 6502 - того самого, который был ЦП в NES, то есть денди.
Процессор этот действительно довольно прямолинейно расширяет 6502 до 16-бит - настолько прямолинейно, что сохраняет при этом все его основные и преимущества и недостатки.
Главный недостаток - всего 4 регистра общего назначения - аккумулятор, два индексных регистра X и Y и указатель стека. Шина адреса, однако, была 24-битной и перед процессором вставала классическая задача - как адресовать всё что выше 16-битной разрядности адреса, т.е. выше 64Кб. Решение было тоже довольно типовым - два 8-битных регистра страниц - DB для данных и PB для кода (data bank и program bank). Содержимое этих регистров просто вставлялось в качестве верхних 8 бит 24-битного адреса - PB при извлечении очередной инструкции из памяти и DB в остальных случаях. Таким образом, кроме необходимости, всё время перекладывать данные в память и обратно из-за малого количества регистров мы имеем сегментацию памяти.
Интересно тут посмотреть как выкрутились из этой ситуации создатели SNES проектируя карту памяти девайса.
Ведь понятно, что огромную часть времени процессору надо и обращаться к ПЗУ картриджа и в то же время манипулировать данными в ОЗУ - но если их развести в разные сегменты (а разводить их по сегментам просто необходимо) - то придётся постоянно прыгать туда-сюда, выгружать и перегружать DB, высвечиваются неприятности и по производительности и по геморройности кода.
Создатели SNES всё это прекрасно понимали и выкрутились довольно таки забавным образом - "нарезав" карту памяти девайса на 64Кб-сегменты они сделали это щадящим для программиста образом.
Всего существует 2 схемы этого маппинга памяти картриджа и системы в SNES, они называются LoROM и HiROM. Обилие мапперов как в денди распространения тут не получило - всего было 2 таких вот маппера с завода и программисты сразу решали какой им приятнее. Я расскажу только ключевую идею, лежащую в основании обеих систем - ROM картриджа "нарезалась" на первую сотню сегментов так, что в каждом сегменте маппинг памяти картриджа происходит только на верхние 32Кб адресного 16-битного пространства. Нижние же 32Кб были заняты всегда одними и теми-же 8Кб ОЗУ и портами ввода-вывода. И только последние 2 сегмента в полном 24-битном адресном пространстве содержали полные 128 Кб памяти девайса - именно первые 8Кб из них мапились в каждую страницу "картриджных" отображений.
То есть можно сказать, адресное пространство в основном было нарезано "слоями" из памяти картриджа, которые перемежались первыми 8Кб ОЗУ и портами ввода-вывода - чтобы "пролистывая" ПЗУ страницами по 32Кб всегда было куда записать что-нибудь в ОЗУ и пообщаться с устройствами. И только настроив DB на последние страницы памяти можно было работать со всеми 128 Кб ОЗУ (порциями по 64Кб за раз правда). Вот как то так это выглядело (недословно, только сама идея!)...

1 сегмент
$000000: первые 8Кб ОЗУ + порты ввода-вывода
$008000: первые 32Кб ПЗУ картриджа
2 сегмент
$010000: первые 8Кб ОЗУ + порты ввода-вывода
$018000: вторые 32Кб ПЗУ картриджа
3 сегмент
$020000: первые 8Кб ОЗУ + порты ввода-вывода
$028000: третьи 32Кб ПЗУ картриджа
...
255 сегмент
$FE0000: первые 64Кб ОЗУ
256 сегмент
$FF0000: вторые 64Кб ОЗУ
Можно воспринимать это так, что по дефолту в нижних 32Кб адресного 16-битного пространства находятся первые 8Кб ОЗУ + порты ввода-вывода, а сегментные регистры переключают отображение верхних 32Кб на ПЗУ картриджа (что, кстати, весьма похоже на раскладку памяти NES) - кроме самых последних двух значений при которых порты и ПЗУ из памяти исчезают, а она вся становится ОЗУ - одним из двух банков по 64Кб. Настоящие схемы LoROM и HiROM сложнее, запутаннее и мапятся не совсем на эти адреса, но центральная идея крутится вокруг этого.

Picture Processor Unit (PPU)

Палитра у SNES состоит из 256 элементов в RGB формате 5:5:5, поэтому доступно 32К оттенков и 256 цветов одновременно на экране, но может быть и больше за счёт direct color mode, о котором ниже. Это заметно больше, чем у Mega Drive.

Основная видеопамять в 64Кб так же размещается в видеочипе и доступна центральному процессору только через порты ввода-вывода или DMA. Так же как и мегадрайве есть другие банки видеопамяти, например палитра и там и там хранится в отдельном таком блоке. На SNES так же в отдельный банк памяти выделены описания спрайтов, OAM = object attrubite memory, (у мегадрайва они хранились в основной VRAM) в количестве 128 слотов. Спрайты (как и на мегадрайве) всегда 16-цветные (формат тайлов такой же как и у 16-цветных фонов и будет описан в разделе про них) и дотягиваются до большего числа цветов через селектор палитры в каждой из них - что расширяет количество цветов отображаемых в разных спрайтах одновременно на экране до 128 штук - и это верхняя часть палитры, она как бы разбита на 8 субпалитр.

Слоты спрайтов в OAM 4-байтные, в то время как на мегадрайве они 8-байтные, что оставляет многие биты слотов неиспользованными. В SNES же все биты просто не влезли! В результате и информации на один спрайт хранится меньше и с теми двумя битами на спрайт, что не влезли в основной слот OAM сделали полухак - их упаковали в 32 байта после основного массива OAM и разместили сразу за ним. То есть в OAM хранится 512 байт основного массива по 4 байта на элемент и сразу за ним - 32 байта упакованных по 2 бита на спрайт. При этом один из этих бит - это верхний бит координаты X спрайта.

Но это еще не всё - на сеге каждый спрайт мог варьироваться в размерах независимо по каждой из осей от 1 до 4 тайлов. На SNES спрайты тоже могут варьироваться в размере уже кратными тайлу порциями - 1, 2, 4 и 8 штук, вроде бы больше даже возможностей - но нет, в описания спратов влез всего 1 бит касающийся размера - поэтому одновременно на экране разные спрайты могут иметь на выбор только один из двух пресетов размеров - первый или второй. А что именно это за размеры выбирается в регистрах видеоконтроллера. В одном кадре просто не может быть более двух разных размеров спрайтов, что не очень весело.
Таким образом, хотя и в сеге всего 80 спрайтов может быть на экране против 128 спрайтов у SNES, но распорядится их размерами она ими может более рационально. Если у сеги нужные к отрисовке спрайты соединяются в связный список, то у SNES опять таки нет на это бит - спрайты просто рисуются все и всегда, ненужные просто надо выводить координатами за край экрана (как и в NES).

Так что по спрайтам SNES если и выигрывает, то незначительно (если вообще не проигрывает).
Но вот на задних фонах (слоях) SNES вырывается вперед.
Во первых - самих слоёв может быть до 4-х штук (в мегадрайве только 2).
Во вторых слои могут быть разной цветности: 4, 16 и 256 цвета (2, 4 и 8 бит на пиксель соответственно).
Состав и битность слоёв определяется текущим видеорежимом, которых может быть 8 штук, их можно составить в таблицу:

Режим    Бит/слой  OPT
         1 2 3 4      
-=-------=-=-=-=----=--
 0       2 2 2 2    -
 1       4 4 2      -
 2       4 4        +
 3       8 4        -
 4       8 2        +
 5       4 2        -
 6       4          +
 7       8          -
Следует помнить, что битность слоя - это битность его тайлов, но в самом слое в его (2-х байтных) плитках хранится как номер тайла, так и 3-битный селектор палитры, поэтому, например, в разных плитках 16-цветного слоя может отображаться до 128 разных цветов (это уже первые 128 элементов палитры). Не более 16 цветов может быть отображено лишь в одной плитке. Это было справедливо и для мегадрайва - только там было всего 4 субпалитры (с 2-битным селектором) и соответственно 64 цвета - одни и те же и на фоны и на спрайты.
Тайлы в SNES так же имеют базовый размер 8x8 пикселей, но каждый из слоёв может быть переведен в режим "тайлы 16x16", при этом он в каждой плитке соединяет 4 соседних тайла из таблицы тайлов (за базовый, чей номер указывается, берется верхний-левый).
Т.к. цветности тайлов у разных слоёв могут быть разные, то и таблицы тайлов у них тоже разуемеется могут быть разные - поэтому для каждого слоя отдельно можно выбрать базис в VRAM где начинается его таблица тайлов. В одном слое при этом может быть отображено максимум 1024 разных тайла, т.к. номер тайла в плитке является 10-битным числом (в мегадрайве - 11-битным).
Каждому фону так же задаётся где в VRAM хранится массив плиток, что он отображает. Они могут иметь размерности 32x32, 64x32, 32x64 и 64x64. В норме это даёт виртуальные размеры фонов как максимум - 512 пикселей по тем или иным осям, но если включить у фона режим "плитка 16x16", то максимальный размер плиточного фона в пикселях увеличивается до 1024x1024.
В режимах, где в колонке "OPT" стоит "+" есть интересная опция - Offset-Per-Tile - потайловое смещение. Эта штука возможно навеяна построчно-колоночными прокрутками из Мегадрайва. Оно работает очень похожим образом - величины прокруток указываются пораздельно, но уже пораздельно для каждой плитки экрана! При этом эти смещения записываются в плитки первого по таблице неотображаемого фона - он начинает использоваться как данные для прокрутки.
Эффект этот довольно мощный, комбинируя и модифицируя двумерный массив прокруток синусоидальными полями можно добиваться весьма впечатлящего "колыхания" поля, например как в игре Yoshi's Island (должно начаться с 4:05):

Границы тайлов полностью смазываются при таком обильном поле смещений и начинает даже казаться, что эффект попиксельный (но он всё-таки потайловый).
Однако, много выше Yoshi's Island была замечена в использовании чипа расширения Super FX GSU-2, как говорится возможны варианты - возможно как раз этот чип вносил посильную помощь в обновление такой таблицы.

Теперь сразу выскажу все прочие полезные SFX, которые в видеоконтроллере были:
- Два "окна" - две пары вертикальных границ лево-право которые могли наложить эффект просветления/затенения или замены цвета в своих границах. Подменой их параметров в каждой строке можно было добится любых сложных форм - так например делается голливудский эффект "That's all folks!", когда картинка исчезает в уменьшающемся кружке, окруженном чёрной заливкой.
- Direct Color Mode - для фонов с 256 цветами можно было включить режим "Direct Color Mode" в этом режиме цвета в каждой плитке формировали RGB-значения по формуле: RRRr0:GGGg0:BBb00, где RGB компоненты брались из пикселя тайла, а rgb-компоненты являлись битами селектора палитры данной плитки. Это потенциально расширяло пространство цветов одного фона до 2048 цветов одновременно на экране, но как видно обращаться с этим было несколько муторно.
- Мозаика - все фоны и/или спрайты могли "покрываться квадратиками", как бы теряя детализацию кратно пикселям, эффект на мой взгляд довольно бестолковый, но его нередко можно было встретить в играх при смене уровня
- Color Math - попиксельные цветовые эффекты, чаще всего применялись для эффектов полупрозрачности или сложного затенения. любой из фонов как и слой спрайтов можно "приписать" в одной из двух сущностей - main screen или sub screen. Отображается на экране обычным образом main screen. Но можно было на выбор активировать одну из несколько заранее заготовленных формул того как цвет (покомпонентно как RGB) main screen может взаимодействовать с цветом полученным из sub screen - в том числе, среднее арифметическое, например, что даёт нам эффект полупрозрачности. есть еще сложения/вычитание с насыщением и т.п.

А вот теперь - о плохом. О раскладке тайлов в памяти. И вот тут всё становится немного печально - если у Мегадрайва 16-цветные тайлы 8x8 хранятся довольно компактно и просто - 4 бита на пиксель, по байту на каждые 2 пикселя строки, 4 байта на строку и 8 строк - всего 32 байта.
То вот у SNES с этим всё сложнее.
Начнём с 4-цветных тайлов: у них 2 бита на пиксель, таким образом нужно 16 байт чтобы описать тайл 8x8. Но в первом байте хранятся первые биты каждого пикселя первой строки - 8 штук, а во втором байте - вторые их биты. Таким образом каждую строку из 8 пикселей описывают 2 подряд идущих байта - первый хранит нижние биты каждого пикселя, а второй - верхние. Это называется перемежающиеся слои.
Наращивание же битности происходит так: всякий раз удваивая битность мы просто берем формат предыдущей битности и добавляем к нему точно такой же слой сразу после него, таким образом удваивая количество бит вводом нового слоя.
То есть 16-цветные тайлы образованы как бы двумя подряд идущими 4-цветным тайлами, чьи соответствующие биты пикселей склеиваются. То есть в первом байте хранятся первые биты первых 8 пикселей, во втором байте - вторые их биты, а в 17-ом и 18-ом - третьи и четвертые соответственно.
256-цветные режимы (кроме Mode 7) соответственно повторяют два раза друг за другом тайловую информацию 16-цветных, таким образом количество байт по которым размазаны биты одного пикселя достигает восьми, причём их расположение не линейно.
Думаю понятно, что рендерить в такое на лету не очень удобно.

Отдельным особняком стоит Mode 7 - в нём все первые 32 Кб VRAM отдаются под единственный 256-цветный слой, причём в них хранятся и изображения тайлов и сама тайловая карта, имеющая уже размер 128x128 тайлов.
При этом плитки становятся однобайтными (во всех других видеорежимах одни 2-байтные, чтобы хранить 10-битный индекс тайла, селектор палитры и биты зеркалирования по вертикали и горизонтали), что требует 128x128=16384 байт VRAM, а тайлов таким образом максимально можно адресовать всего 256. Учитывая что на 8-битный тайл 8x8 требуется 64 байта, то 256 тайлов занимают 16Кб - как раз вторую половину из выделенных 32Кб.
Данные на этот раз хранятся абсолютно линейно - однобайтные пиксели всех тайлов следуют друг за другом ровно строка за строкой, будучи "упакованными" в байт-на-пиксель, так и плитки фона образуют двумерный массив 128x128, однако оба этих массива данных побайтово перемежаются друг с другом. То есть каждый четный байт памяти отведен под одно, а каждый нечетный - под другое! В целом всё линейно кроме этого - адреса всех байт одного массива умножены на два, а другого не только умножены, но и увеличены на 1. Почему так - скорее всего дело тут в том, что с точки зрения аппаратуры четные и нечетные байты VRAM разведены в разные банки памяти для ускорения линейного считывания слов - это старый как микросхема трюк. Поэтому видимо видеочип при такой организации памяти может параллельно работать с данными тайлов и с массивом плиток без помех.
Но программист тут опять вынужден мириться - дело в том, что любой доступ к VRAM (это, кстати, справедливо и для мегадрайва!) извне видеочипа (например процессором) происходит только 16-битными словами. Это значит, что для того чтобы изменить только один байт в VRAM надо считать слово по данному адресу, изменить в нём байт и записать обратно. Понятно, что при всём вышесказанном это превращается в дополнительный геморрой.
Главная же фишка Mode 7 - это то, что он может подвергаться произвольным аффинным трансформациям, что совместно с техниками HBlank позволяют крайне разнообразить картинку псевдо-3D.

Заключение и сравнение с Sega Mega Drive

У видеочипа SNES объективно лучше палитра, объективно больше спец-эффектов - всё что у сеги есть - это построчные и постолбцовые прокрутки фонов и просветления/затенения определенных плиток.
SNES же богат на потайловый скроллинг, мозаичный эффект, обрезку по плавающим двум границам двух "окон", direct color mode и попиксельный Color Math, который позволяет реализовать частичные затенения/осветления и полупрозрачность.
Несомненно жемчужина SNES - это Mode 7.
Однако дальше всё не так уж однозначно - спрайтов вроде бы и больше, но их возможности усеченнее.
Фонов вроде бы и больше, но если приглядеться - единственный режим где возможны целых 4 фона - это нулевой, в нём все эти фоны всего лишь 4-цветные, а это цветность как в денди (там правда был всего 1 фон), никуда не годится такое на 16-битных консолях.
Поэтому реально о превосходстве в фонах можно говорить лишь в двух случаях:
- Режим 1, где фонов три, при этом первые два 16-цветные как у сеги, а вот третий, 4-цветный получается "бонусом".
- Режимы с 256-цветными фонами, где самих фонов не больше, чем у сеги - те же 2 штуки, но их цветность выше.
И если теперь окунуться в то как это всё разложено в памяти и как оно неприятно программируется - то плюсы с учетом минусов становятся уже не столь внушительными.

Есть еще один забавный факт, о котором хотелось бы упомянуть - что у видеочипа Mega Drive, что у видеочипа SNES присутствует немало бит в регистрах, которые позволяют те или иные фоны или таблицы тайлов отображать в другие 64Кб VRAM. То есть чипы изначально проектировались под 128Кб VRAM с возможностью отображать в верхние 64Кб или иные вещи. Например тайлы спрайтов можно хранить в одном банке, а тайлы фонов в другом и так далее.
Таким образом, очевидно, что эти же видеочипы видимо использовались где-то еще - скорее всего в аркадных автоматах, уже с полной мощью 128-килобайтного VRAM.

(скомпоновано в одну статью с разрешения Kollect3D)

22 ноября 2017

#16bit, #4generation

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