- Баллон (внутри кинескопа вакуум)
- Электронная пушка
- Отклоняющие катушки
- Электронный пучок (электронный луч)
- Экран со слоем люминофора
- Внутренний проводник для разрядки люминофора.
Как это всё работает. Электронная пушка - устройство испускающее электронный луч (поток электронов) в сторону экрана. Экран кинескопа изнутри покрыт специальным веществом - люминофором которой светится при попадании на него потока электронов. то есть в том месте где луч падает на экран - появляется светящаяся точка. Электронные катушки создают электромагнитные поля способные отклонять луч. Обычно их две. Одна отклоняет луч по вертикали, другая по горизонтали. При помощи изменения характеристик полей мы можем контролировать отклонение луча, а как следствие расположение светового пятна на экране. Помимо положения светового пятна на экране, в кинескопе может регулироваться яркость и цвет этого пятна изменением характеристик самого луча электронной пушкой.
И этих трёх параметров уже хватает для создания изображения. Световое пятно перемещается по всему экрану с огромной скоростью в нужных местах меняя свои яркость и цвет. Это движение настолько быстро что мы видим след от пятна по всему экрану который и формирует изображение. Далее световое пятно я буду называть просто луч, потому что это пятно и создаётся лучом.
Движение луча не хаотичное или очень сложное. Луч заполняет экран изображением из верхнего левого угла, двигаясь по горизонтали до правого края. Горизонтальное движение луча от левого края экрана до правого называется строкой. Затем луч возвращается к левому краю и смещается немного вниз и снова движется к правому краю рисуя следующую строку и так далее пока не окажется в правом нижнем углу экрана. Затем луч перемещается в верхний левый угол экрана. Так отрисовывается каждый кадр изображения. Ниже показана траектория движения луча.
Пока луч возвращается от правого края к левому он "отключается", чтобы не перечёркивать собой уже нарисованное изображение. Этот процесс называется Строчный гасящий импульс. Название говорит само за себя - после отрисовки строки луч "гасится" для перемещения к началу новой строки. По-английски этот термин звучит как Horizontal blanking interval или сокращённо HBLANK именно такой вариант можно часто встретить в технических спецификациях и литературе. Когда луч возвращается в верхнюю левую точку после завершения последней строки, он точно также "гасится" и только после этого перемещается.
Этот процесс называется Кадровый гасящий импульс иногда его называют просто - обратный ход луча. Стоит упомянуть что и HBLANK так можно назвать, но как правило если не уточняется направление, то говорят о вертикальном ходе луча. По-английски этот процесс называется Vertical blanking interval, сокращённо VBLANK.
Все эти интервалы гашения и перемещения луча были созданы лишь по единственной причине. Катушки отклоняющие луч не могут его позиционировать мгновенно, либо с такой высокой скоростью, что она могла бы считаться условно мгновенной, поэтому данные периоды были введены - для компенсации инерции отклоняющего механизма.
Форматы передачи видео целиком следовали принципам работы кинескопа. По своей сути передача изображения сводилась к последовательности команд для отклоняющих магнитов и электронной пушки. Таким образом исходное изображение разбивалось на строки состоявшие из последовательных команд смены цвета и яркости электронной пушке. Строки шли одна за другой разделённые специальной меткой, которая и называлась строчный гасящий импульс. Последняя строка в кадре оканчивалась кадровым гасящим импульсом, после которого начинался новый кадр видео.
Понимание что такое строка и периоды обратного хода луча нам понадобятся в будущем для работы с видеопроцессором SMS.
Глубина цвета.
Вторая важная характеристика цифрового изображения - глубина цвета которую иногда ещё называют разрядность цветовой палитры. Глубина цвета измеряется в битах и позволяет нам узнать в какое количество вариантов цветов может отобразить каждый пиксель. Например если глубина цвета - 1 бит, то каждый пиксель в изображении может быть только одного из двух цветов (обычно чёрный и белый). При глубине цвета в 2 бита - получаем 4 варианта цветов, 3 бита - 8 цветов, 4 бита - 16 цветов и так далее. Посчитать очень просто - 2^n, где n - глубина цвета в битах.
Теперь давайте посчитаем сколько нужно памяти чтобы хранить изображение 256×192 пикселей с глубиной цвета в 8 бит. Информация о цвете в одном пикселе будет занимать 1 байт. Значит всё изображение займёт в памяти 256×192 = 49152 байт или 48кб. По современным меркам - сущие пустяки. Но и ресурсы SMS по современным же меркам тоже весьма скромны. Всё адресное пространство процессора z80 составляет 64кб, а ёмкость носителя Sega Card и того меньше - 32кб.
Тайлы
Давайте разбираться как же это всё оптимизировано в SMS. Возьмём кадр из одной из моих любимых игр Phantasy Star.
Если присмотреться повнимательнее, то вы увидите определённую повторяемость картинки. и именно этот момент является ключевым в понимании того как строилась графика на всех приставках того периода.
Давайте я добавлю разделительных полос чтобы стало очевиднее.
Сразу видно что общая картинка состоит из маленьких фрагментов одинакового размера, а разнообразие этих фрагментов невелико. Такие фрагменты называются тайлами. Слово тайл это транскрипция английского tile - и переводится как "плитка". И это действительно похоже на выкладывание плиткой итогового большого узора. Давайте теперь посчитаем. Размер итоговой картинки 248×192 пикселей, размер тайла - 8×8 пикселей, если представить в изображение в тайлах, то получится 31×24 тайла. Теперь представим себе что количество различных тайлов у нас к примеру 256, Значит один тайл можно будет выразить одним байтом информации. Получается что картинка занимает 31×24 = 744 байта. и отдельно хранящаяся таблица с самими тайлами, которая называется тайлсет (англ. tileset). Если каждый тайл - это маленькая картинка 8×8 пикселей, и таких картинок у нас 256, и объём занимаемый самими тайлами будет 8 * 8 * 256 = 16384 байт = 16кб. Итоговый объём получается чуть меньше 17кб.
Мы сократили объём занимаемой памяти почти в четыре раза, по сравнению с хранением изображения "попиксельно". Но это ещё не всё. Ведь используя уже имеющийся набор тайлов мы можем составлять из них и другие изображения и каждое новое изображение будет занимать всего по 744 байта. Таким образом при большом количестве изображений экономия получается весьма и весьма значительной.
Теперь давайте обратим более пристальное внимание на глубину цвета. В примерах выше мы рассматривали глубину цвета в 8 бит, то есть 256 вариантов цветов. SMS способна выдавать несколько меньше - 64 цвета. Это 6 бит, но на общие рассчёты это не повлияет, потому что цвет в SMS по прежнему кодируется одним байтом в котором используются только 6 из 8 бит.
Палитра
На данный момент таблица тайлов занимает 16кб. Попробуем уменьшить размер занимаемый таблицей с тайлами в памяти. Как видно из нашей картинки, тут явно не все 64 цвета. Просто сокращать глубину цвета будет не очень хорошей идеей, а вот сократить количество одновременно используемых цветов в рамках одного изображения - уже интереснее.
Мы можем выбрать из имеющихся 64 цветов, например 16 цветов. И пользоваться только ими в рамках одного набора тайлов. Такой набор из 16 цветов называется палитрой. Теперь мы можем в каждом тайле представлять цвет не его непосредственным кодом, а его номером в палитре. Диапазон палитры (16 цветов) будет занимать всего 4 бита, поэтому одним байтом мы можем закодировать сразу 2 пикселя в тайле. Этим самым мы уменьшим объём занимаемой памяти таблицей тайлов ещё в 2 раза, получив 8кб. Всё что нам осталось - понять как из номера цвета в палитре получить сам код цвета. И это довольно просто - мы заведём ещё одну таблицу - таблицу палитры, которая будет занимать 16 байт и где каждый байт будет представлять код цвета из общего диапазона который выдаёт приставка.
Использование палиты даёт интересные возможности, потому что мы можем использовать тот же самый набор тайлов, только изменить реальные цвета в самой палитре и тогда набор тайлов сразу "перекрасится". Этот приём использовался в огромном количестве игр. А так же изменение палитры использовалось и для анимации, например "миганющих" врагов или персонажей.
Давайте подытожим. Всё изображение состоит из тайлов 8×8 пикселей, в которых цвет каждого пикселя берётся из 16 цветовой палитры. По такой схеме мы можем переиспользовать одни и те же элементы в построении огромного количества разных изображений которые будут занимать очень мало дополнительного места в памяти. Такой метод называется тайловой графикой. И практически во всех консолях того времени использовался именно этот метод. Ну и для в качестве финала разъяснения этих базовых понятий давайте посчитаем окончательную разницу в объёмах памяти с использованием тайловой графики и без.
- Цвет кодируется одним байтом.
- Разрешение изображения 248×192
- Возьмём максимальное количество различных вариантов тайлов в одном изображении - 512. Но кодировать тайл будем двумя байтами как в SMS. Почему именно двумя - разберёмся чуть позже.
Без тайлов - 248×192 = 47616 = 46.5кб
С тайлами -
- Изображение - 248×192 = 31×24 тайла = 744 тайлов * 2 байта = 1488 байт
- Набор тайлов - 512 * 32байта = 16384 байт = 16кб
- Палитра - 16 байт
Общее количество занимаемой памяти: 16кб + 1488 байт + 16 байт = чуть меньше 17.5кб
Спрайты
Мы разобрались как строить изображения из тайлов. Но у такого подхода, помимо очевидных плюсов, есть и свои минусы. Все Тайлы в изображении выстроены в сетку равную размеру тайла. И если объекту надо быть вне этой сетки, то сразу возникает проблема. Например персонаж который свободно ходит по экрану или летящие во врагов или от врагов пули. При достаточно быстром движении такого объекта, когда за один кадр он смещается на 8 пикселей, можно выкрутиться и в рамках сетки, но такой случай скорее исключение.
Здесь к нам на помощь приходят спрайты. Историческое определение спрайта гласит, что это небольшое изображение отображаемое поверх общей картинки. В этом определении довольно мало конкретики, поэтому давайте сразу перейдём к нашему случаю и в итоге уточним это определение. Как правило, в играх для SMS и других платформ того же периода все визуальные составляющие можно разделить на две условные категории, первая категория - это фон, вторая - активные объекты, такие как игрок, враги, выстрелы, взрывы, предметы и так далее. Фон обычно максимально статичен внутри себя, то есть он может целиком прокручиваться следуя за игроком, но его объекты остаются неподвижными относительно друг друга. Игровые объекты же находятся в постоянном движении относительно друг друга и фона.
Исходя из такой ситуации мы так же можем разделить изображение на два слоя: Фон и объекты. Фоновый слой мы можем смело выполнять в разобранной выше тайловой технике, а все объекты требующие более точного позиционирования - будем уже отображать в слое объектов, который находится поверх фонового. Изображение для такого будем брать из той же самой таблицы тайлов, но к примеру с другой палитрой. Отличие лишь в том что координаты такого объекта мы можем задавать с точностью до пикселя. Такой объект и будет называться Спрайтом.
Со всей новой информацией попробуем дать более точное определение спрайту. Спрайт - это тайл отображаемый поверх фонового изображения и спозиционированый с точностью до пикселя, как правило, имеющий отличные от фона аттрибуты (например палитру).
Видеопроцессор
Теперь попробуем всю полученную информацию применить на практике и разобраться как именно подобная система устроена в SMS. Как я упоминал в самом начале, приставки того периода были спроектированы специально чтобы выводить уже готовый аналоговый сигнал, будь то композитное видео или даже RF-сигнал.
Генерация видеосигнала "на лету" - это очень сложное занятие из-за его скорости и необходимости в жесткой синхронизации по времени. Поэтому наиболее оптимальным решением было видео систему независимой от центрального процессора. Этим и занимается видеопроцессор или VDP (англ. video display processor).
VDP - это специализированная микросхема для формирования видеосигнала выходящего из приставки. Она оптимизирована как раз для работы с тайловой графикой. В отличие от центрального процессора, VDP нам программировать не придётся. Так как он всегда работает по одному и тому же алгоритму который в него заложен при проектировании. Мы можем лишь передавать ему необходимую графическую информацию и менять некоторые настройки. В VDP существует 11 регистров, они похожи на регистры специального назначения в z80. Помимо регистров существует ещё два типа памяти CRAM и VRAM. CRAM - это встроенная в VDP память состоящая из 32 байт и служит для хранения палитр. VRAM - внешняя память объёмом в 16кб подключенная напрямую к VDP, которая служит для хранения текущего изображения в тайлах, тайлсет и спрайты. Доступ к VRAM есть только у VDP, центральный процессор к ней просто не подключен и может читать и писать в эту память только через VDP. Для общения VDP и центрального процессора отведено 2 порта, работу с которыми мы будем рассматривать уже на практике.
Характеристики
У VDP есть четыре режима работы, Но рассматривать мы будем только 4й. Всё дело в том, что VDP является развитием видеоконтроллера TMS9918 использовавшегося как в более ранних консолях от Sega так и в других компьютерах того времени. И первые три режима существуют для обратной совместимости с ними. Итак, какие возможности предоставляет нам VDP:
- Разрешение - 256×192, 256×224 и 256×240 в зависимости от настроек
- Палитра - две палитры по 16 цветов, 32 одновременных цвета на экране из доступных 64 цветов.
- Тайлы - размер тайла 8×8 пикселей, 16 цветов, максимальный размер тайлсета 448 тайлов
- Фоновый слой - максимальный размер изображения 32×28 тайлов, в некоторых ревизиях SMS - 32×32 тайлов.
- Аппаратный скроллинг - По вертикали, горизонтали и диагонали
- Слой спрайтов - поддержка до 64 спрайтов (до 8 спрайтов на строку)
- Генерация прерываний
Если что-то из этого непонятно - не беда. Мы сейчас разберём все пункты в подробностях.
Палитра
В составе VDP есть специальная память - CRAM (Color RAM). CRAM имеет объём 32 байта, что позволяет ей хранить две палитры по 16 цветов. Первая палитра в байтах 0-15 используется для тайлов в фоновом слое, вторая палитра для спрайтов и для фоновых тайлов, если это явно указано. Иногда я буду называть эти палитры как тайловая и спрайтовая или же по номерам: первая и вторая.
Разрешение экрана
С разрешением экрана есть одна интересная особенность. Разрешение обрабатываемое VDP и видимое на экране телевизора - различаются. На экране реально отображается область немного меньшая чем доступна в VDP. Видимую область назовём видимым экраном, а полную область виртуальным экраном. Это было сделано для облегчения работы с прокруткой экрана. Вспомните как почти во всех играх, когда игрок куда-то идёт или едет - экран движется вместе с ним. Этот эффект достигается смещением фонового слоя. И для того чтобы не было рваных краёв. Смещаемая картинка должна быть минимум на 1 тайл больше по ширине и/или высоте, в зависимости от того в каком направлении идёт смещение. Взгляните на изображение и всё станет ясно. Видимый экран обозначен чёрной рамкой.
VDP позволяет путём изменения значения своих регистров немного менять разрешение экрана. Существуют 2 варианта расширения видимого и виртуального экранов. В стандартном режиме видимый экран 32×24 тайла, виртуальный 32×28 тайлов. При настройках расширения экрана. высота виртуального экрана становится 32 тайла, а высота видимого экрана становится 26 или 30 тайлов. Помимо изменения высоты существует независимая возможность уменьшить ширину скрыв самый левый столбец. Однако режимы с размером виртуального экрана недоступны на первых ревизиях SMS из-за объёмов VRAM. В следующих постах мы будем рассматривать в основном только стандартный режим с высотой виртуального экрана в 28 тайлов.
Содержимое виртуального экрана хранится в видеопамяти в виде непрерывного участка данных в которых каждый тайл представлен двумя байтами. Говоря об этой области в памяти я так и буду называть её виртуальный экран или таблица экрана.
Каждый тайл виртуального экрана представлен двумя байтами,
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
x | x | x | o | p | v | h | t | t | t | t | t | t | t | t | t |
- t - 9 битное число. номер тайла в тайлсете 0-511
- h - Отображать тайл зеркально отораженным по горизонтали
- v - Отображать тайл зеркально отораженным по вертикали
- p - Использовать для этого тайла палитру спрайтов.
- o - Отображать тайл поверх спрайтов.
- x - Эти три бита не используются. И некоторые игры используют их для хранения своих данных
Биты o, p, v, h называются аттрибутами тайла в таблице экрана. Как вы уже наверное догадались биты v и h позволяют дополнительно сэкономить место в тайлсете, используя один и тот же тайл в 4 возможных вариантах. Аттрибут o позволяет рисовать тайл поверх спрайтов. Что позволяет персонажам и иным объектам "прятаться" за элементами фона, создавая иллюзию многослойности. Виртуальный экран занимает в памяти 32×28 тайлов * 2 байта = 1,75кб
Тайлы
Размер тайла в VDP неизменен и всегда равен 8×8 пикселей. Цвет пикселя кодируется четырьмя битами, то есть 8×8 = 64 пикселя / 2 = 32 байта. Тайлсет хранится в VRAM. Максимально количество тайлов в тайлсете - 448 и занимают они 14кб из 16кб всей видеопамяти.
Спрайты
Максимальное количество спрайтов поддерживаемое VDP - 64, однако есть и иное ограничение, одну строку не могут пересекать больше 8 спрайтов одновременно. Всё остальное рассказанное о спрайтах применимо и здесь. В VRAM хранится три параметра спрайтов. координата x, координата y, однобайтовый индекс тайла. Эта область называется таблицей спрайтов. Но погодите как однобайтовый? Ведь тайлов больше и как мы уже знаем для того чтобы хранить информацию о тайле, необходимо 9 бит, а не 8. Всё верно. Спрайты могут использовать только перые 256 тайлов, либо только тайлы с 257 до 448. В одном из регистров VDP есть специальный бит определяющий ту половину тайлсета из которой берутся тайлы для спрайтов. Этот бит можно рассматривать как 9й старший бит в номере тайла используемого спрайтом. То есть реальный номер тайла используемого спрайтом складывается из этого специального бита и номера хранящегося в таблице спрайтов.
[ ------ Реальный номер тайла ------ ]
1 | | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 = %101101000 = 360 |
8 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Дополнительный Байт в таблице спрайтов
бит в регистре
Таблица спрайтов расположена в оставшихся 256 байтах VRAM. Расположение данных в ней немного не интуитивно. Первые 64 байта содержат координаты спрайтов по вертикали. Затем идут 64 неиспользуемых байта. И дальше идёт последовательность из пар байт содержащих координату по горизонтали и индекс тайла. Получить данные о конкретном спрайте по его порядковому номеру можно следующим образом.
- координата y = n
- индекс тайла = x + 1
Где n - номер тайла.
Как я упоминал в кратком списке возможностей - у спрайтов есть дополнительное ограничение. Не более 8 спрайтов на строку. Это означает что каждую отдельную строку не может пересекать больше 8 спрайтов. Перед отрисовкой каждой строки VDP считывает поочереди Y координаты всех спрайтов и смотрит пересекает ли спрайт следующую строку. В случае если пересекает - помещает спрайт во внутренний буфер, который может вмещать не более 8 спрайтов. Если буфер заполняется целиком, то vdp останавливает обработку всех дальнейших спрайтов и выставляет специальный бит в байте-статусе который мы можем прочитать через специальный порт. Когда VDP переходит к отрисовке этой строки, то уже берёт данные о спрайтах из буфера и "смешивает". Данное ограничение введено скорее всего из-за того что VDP просто не успевает смешивать за время строки фон и больше 8 спрайтов.
А вот так выглядит расположение всех данных в VRAM:
Скроллинг
В разделе про разрешение экрана мы коснулись прокрутки экрана. Теперь давайте посмотрим на эту возможность поближе. VDP позволяет нам управлять смещением фонового слоя относительно слоя спрайтов. Для смещения по одной из осей у нас есть два рычага управления. Мы можем указывать точное смещение слоя в пикселях от 0 до 7 влево или вниз в зависимости от того с какой из осей мы работаем и указывать стартовый тайл в таблице экрана, например мы можем указать начало с 5-го тайла, тогда в строке будут нарисованы тайлы с 5 по 32. а после 32 тайла выведутся тайлы с 1 по 4й из этой строки. Подобный механизм существует как для горизонтальной оси так и для вертикальной оси. Значения смещений мы можем указывать меняя значения специальным регистрам VDP.
Регистры
В этой статье я довольно часто писал о регистрах VDP. Теперь давайте обзорно разберём их количество и назначение. VDP имеет десять регистров, многие из них работают как наборы флагов, то есть числа хранящиеся в них имеют смысл только при побитовом разборе. Некоторые флаги и даже целые регистры в VDP являются наследием предшествующих моделей и остались только для совместимости или унификации общего дизайна, так что требуют только какого-то одного значения для корректной работы.
Регистр #0 - Первый регистр графического режима.
- Бит 0 - Внешняя синхронизация. Этот бит всегда должен быть 0.
- Бит 1 - M2 Включение расширенного виртуального экрана
- Бит 2 - M4 Использовать 4й графический режим (Mode4) или использовать режимы 1-3
- Бит 3 - EC Сдвинуть спрайты влево на 8 пикселей.
- Бит 4 - IE1 Включить прерывания строк
- Бит 5 - Скрыть нулевую колонку. Она будет нарисована цветом из регистра #7
- Бит 6 - Отключить горизонтальный скроллинг для строк тайлов 0-1
- Бит 7 - Отключить вертикальный скроллинг для колонок тайлов 24-31
Биты 3 и 5 удобно использовать в связке когда требуется постоянный горизонтальный скроллинг. При помощи бита 5 Мы можем отключить отрисовку первой колонки тайлов, что создаст для нас невидимую зону для запасного ряда тайлов, тогда визуально началом экрана станет вторая колонка. Но координаты спрайтов будут по-прежнему учитывать скрытую колонку. И тут нам на помощь приходит бит 3, он автоматически компенсирует координаты спрайтов по горизонтали.
Регистр #1 - Второй регистр графического режима.
- Бит 0 - Увеличение спрайтов в два раза.
- Бит 1 - Спрайт использует два тайла. В этом режиме спрайт состоит из двух тайлов, которые расположены один над другим. В качестве второго тайла берётся сделующий из тайлсета после того что указан в таблице тайлов.
- Бит 2 - Не используется
- Бит 3 - M3 Этот бит отвечает за активацию режима с увеличеной высотой экрана до 30 тайлов. Используется только в последних ревизиях SMS. И эта настройка имеет смысл тольков 4м графическом режиме.
- Бит 4 - M1 Аналогично биту 3, но для высоты экрана в 28 тайлов.
- Бит 5 - IE0 Включение генерации прерывания во время наступления VBLANK
- Бит 6 - Вывод изображения. Если этот бит равен нулю, то VDP перестаёт выдавать изображение.
- Бит 7 - Не используется
Регистр #2 - Регистр адреса таблицы экрана
Данный регистр содержит настройку того с какого адреса в VRAM начинается таблица виртуального экрана. По умолчанию его значение должно быть $FF, что соответствует адресу $3800.
Регистры #3 и #4 - не используются и их значения всегда должны быть $FF.
Регистр #5 - Регистр адреса таблицы спрайтов.
Данный регистр содержит настройку того с какого адреса в VRAM начинается таблица спрайтов.
Регистр #6 - Регистр адреса тайлсета.
Данный регистр содержит настройку того с какого адреса в VRAM начинается тайлсет. В этом регистре имеет значение только пятый бит. Если он равен нулю, то тайлсет будет начинаться с адреса $0000, если единице - то с адреса $8000. Остальные биты должны быть равны единице.
Регистр #7 - Цвет фона.
Биты с 0 по 4 содержат индекс цвета в таблице спрайтов. Он и будет использоваться в качестве фонового цвета.
Регистр #8 - Регистр горизонтального скроллинга.
Биты с 0 по 2 используются для точного смещения фона в пикселях. Остальные биты указывают на стартовый тайл в строке.
Регистр #9 - Регистр Вертикального скроллинга.
Аналогичен регистру #8, только для оси Y.
Регистр #10 - Счётчик прерываний.
На этом пожалуй всё. Более тонкие подробности и особенности работы с VDP мы будем рассматривать непосредственно на практике. А в следующем посте мы начнём уже разбираться с ассемблером.