Если у чего-то есть экран, то там обязана играть Bad Apple!. Именно так думал и я. Каково же было моё удивление, когда узнал, что Bad Apple не существует на MSX, и вот почему... прошу под кат...
Когда-то давным-давно моим первым компьютером стал школьный КУВТ-2, он же "Ямаха". Это как первая любовь, такая же недолгая, прошло несколько лет обучения MSX-Basic, и далее мы перешли на x286, но ощущение, что что-то осталось нереализованным на этом компьютере - осталось. Конечно же, в те годы, пятиклашками, мы не понимали, чем одни компьютеры отличались от других, но любимым времяпрепровождением было пялиться в зелёный экран и нажимать на клавиши клавиатуры, а также иногда играть в USAS, Vampir Killer и Metal Gear...
С тех пор прошло много времени, и я почему-то и не предполагал, что можно вернуться в прошлое, вновь ощутить в руках клавиатуру КУВТ-2, увидеть приветствие MSX Basic... Но внезапно, в прошлом году увидев на барахолке КУВТ, я неожиданно для себя купил её... Строго говоря КУВТ-2 - это MSX-2 в варианте поставки от Ямахи для России в учебные классы, с небольшими модификациями. И это классно! Так как MSX, как платформа, в мире очень распространена, а в Японии так и вообще есть культ MSX.
Очевидно, что в своё время я не успел написать на ней что-то важное для себя, понять вообще, как эта Ямаха работает, и нужно было восполнить этот пробел... Но сперва нужно посмотреть, что уже есть и сыграть в любимые игры. Когда эйфория уже спала, то начался этап предметного изучения и поиска идеи, что же такого закодить. Ранее с друзьями мы уже развлекались зеленой демкой Bad Apple на осциллографе, и тут подумалось: "А не замахнуться ли нам на Вильяма, понимаете ли, нашего Шекспира?":
По какой-то невероятной причине Bad Apple не была представлена на платформе MSX, то есть формально какая-то демо была, но запустить его на не разогнанной не модифицированной MSX было нельзя, существует стрим 80Мбайтного файла со специализированного накопителя на специально модифицированной MSX, короче, это было явно не то, что требовалось...
Почему же Bad Apple нельзя реализовать на MSX? Ответ кроется в архитектуре платформы MSX. Из книги К.И.Фахрутдинов, И.И.Бочаров "Архитектура и устройства микрокомпьютеров стандарта MSX-2":
Сердцем MSX является Z80 процессор (привет "здесь смайл машет вам ручкой" почитатели ZX Spectrum), но! как мы видим на схеме, видео память VRAM прячется за видео процессор TI V9918/38, что резко снижает скорость пересылки данных из обычной памяти в видео память. В то же время, наличие видеопроцессора является и преимуществом в архитектуре MSX, он позволяет выполнять различные операции по отрисовке линий, точек, а также отображать тайлы (они же шаблоны, они же символы) и спрайты, позволяет отслеживать коллизии спрайтов, а также ещё несколько ништячков, что очень полезно при реализации игр. Но, к сожалению, они не помогают при выводе видео. Нужно было придумывать некоторую хитрость.
Были проверены варианты отрисовки изображения точками и линиями, но они оказались слишком медленные, и тогда стало понятным, что нужно решение искать в родной области MSX - а именно в отображении тайлов. По сути всю область экрана (256х192) можно представить как знакоместа 32х24, в каждом знакоместе можно разместить одни из 256 символов 8х8, он же тайл. Таких тайлов может храниться в памяти VRAM 256 штук. 256 тайлов не могут покрыть все варианты реализации 8х8, но могут перекрыть все варианты 4х2, что должно соответствовать разрешению 128х48.
Таким образом, решение для отображения видео в тайловом (символьном) формате лежит в подборе таких тайлов, чтобы они были максимально естественными и создавали ощущение чуть большего разрешения, чем на самом деле.
Чтобы чутка упростить себе жизнь, большая часть различных преобразований, забивая гвозди микроскопом - выполнена в матлабе. Все варианты точек разрешения 4х2 в тайле 8х8 были размазаны фильтром и обратно биннаризованы. Вот для примера, как это происходит, тайл 00110010 сначала размывается, потом опять биннаризируется и "ступеньки" получаются чуть более сглаженными. А также на рисунке ниже - все варианты тайлов грубые и сглаженные.
Дальше нужно было решить магию, как получившийся объем из 2200+ кадров уместить в размер хотя бы одной дискеты, не более чем 720кБ. Каждый кадр теперь это 32*24 = 768 байт или на видео 1.5Мбайт. Нужно было бы придумать очень простой вариант кодера. После переборки нескольких вариантов было решено ввести следующие четыре состояния битового потока: рисовать белым (код 0b00), рисовать черным (0b11), отрисовать следующие N пикселей текущим пером (0b01) и отрисовать в текущем тайле следующий в потоке 8 битный символ (0b10). Такая простая реализация кодирования позволила реализовать на Z80 простой декодер и уменьшить объем данных с 1500кБ до 320кБ.
В самой реализации кода под Z80 MSX все относительно просто:
инициализация
чтение первичного 16кБ буфера видео
основной цикл
ожидание кадрового импульса
вывод следующего семпла звука
отображение следующей части кадра в текущем видео буфере
чтение данных из буфера
декодирование
отрисовка тайла (пересылка в видеопроцессор(VDP)-память(VRAM))
если осталось время - дозаполнение буфера видео с дискеты
конец основного цикла
Результатом всего этого действа стала первая Bad Apple демо как для MSX-2, так и для более простых MSX машин. =) Хочу выразить признательность организаторам Chaos Construction 2021 за возможность представить эту работу общественности на демопати!
приятного просмотра:
Файл демо можно скачать на poeut.net:
или посмотреть в web player msx online:
или посмотреть, как в Японии играет демо на MX-10 (одна из самых простых версий первых MSX компьютеров)
Хочу выразить признательность всему сообществу MSX, которое меня поддерживало и помогло влиться в архитектуру MSX за такое короткое время: Страйстару, Дельфину и Серому, а так же всем в сообществе world_of_msx и канала дискорд "MSX по-русски!", без вас я бы не смог и большей части кода реализовать, и уж тем более ту хорошую оптимизацию по чтению и отображения данных, что получилась в этой работе.
В дополнение: что для себя я вынес работая над этой демо: оказывается, ранее, в играх 80х для MSX, вполне могла бы быть доступна возможность отображения аналогичного видео в заставках, между уровнями, что добавило бы им некоторой изюминки. =)
В дополнение 2: можно ли сделать данную реализацию более качественной? Да, безусловно, но, к сожалению, на все не хватает времени. Фактически в видео из 256 тайлов не используются совсем 13 тайлов и около 25 используются однократно, такие тайлы можно (и нужно) использовать для повышения разрешения наиболее востребованных элементов изображения. Также возможны дополнительные математические оптимизации, но это пусть будет в других работах.
Комментарии (21)
horror_x
07.01.2022 22:46+1Нужно было бы придумать очень простой вариант кодера.
А не рассматривали CCITT T6? Не требует много ресурсов и эффективно сжимает монохромные изображения. Использовался для факсимильных сообщений.DolphinSoft
07.01.2022 22:49+1Проблема в том, что это не изображение.
В том смысле, что это тайловый режим, и каждый кадр пересылаются в видеопроцессор не тайлы, а таблица имен (так называемая тайловая карта)horror_x
07.01.2022 22:55Понял. Мне сначала показалось, что это тайлы декодируются, а это речь уже о картинке из тайлов (точнее из их фрагментов).
DolphinSoft
07.01.2022 23:07+2Экран 32х24 строится из 768 знакомест (тайловая карта таким же размером в байтах), в которые выбираются тайлы из таблицы.
Есть несколько режимов. В простом, есть 256 тайлов, таблица шаблонов которых занимает 2048 байт.
Проблема этого режима в том, что 256 тайлов не покрывают всю таблицу имен уникальными тайлами.
И есть более широкий режим с 768 уникальными тайлами (уникальный набор для каждой 1/3 экрана), которые покрывают весь экран, но размер таблицы шаблонов становится равным 6144 байта (и 2048 байт на таблицу цветности).
Приходится выбирать: пересылать 768 байт в простом режиме, или 6144 - в псевдо-растровом.horror_x
07.01.2022 23:16+1Мне вспоминается Sonic 3D Blast. Там для видео в интро, если память не изменяет, использовали несколько трюков: неполное покрытие экрана, прореженную частоту кадров, черезстрочную развёртку и дублирование строк с помощью прерываний (изображение было сжато по вертикали в два раза).
DolphinSoft
07.01.2022 23:20+3Да, MSX2 все это умеет (не MSX1), причем возможностей даже больше чем у NES.
Жаль что таких игр и таких эффектов почти никто не делал.
Платформа позволяет чудеса! :)
grishkaa
08.01.2022 08:11+1Вот прочитал я это, и снова теперь меня мучает эта мысль запустить bad apple на телефоне Siemens CX75, который у меня валяется и до сих пор вполне себе работает (только батарейка почти не держит). Причём не на J2ME — это для слабаков и будет лагать — а нативно, эльфом, напрямую в буфер экрана. Осталось «всего лишь» понять, чем эти эльфы можно компилировать в 2022 году на macOS и дореверсить нужные части прошивки.
Gummilion
08.01.2022 15:31+1Получается, у вас каждый кадр кодируется и отображается независимо, а что, если сделать как в видеокодеках: I-кадры с полной информацией и P-кадры с разницей с предыдущим?
Pyhesty Автор
08.01.2022 15:58+2это проверялось, но получается, что в среднем кадры отличаются друг от друга на 60-100 элементов (тайлов), а в некоторых случаях кадры резко отличаются (когда происходит инверсия например или поворот персонажа), в среднем различие 60-100 байт - и это только данных, но нужно также сохранить адрес где произошло изменение и полученный массив уже очень плохо упаковывается и затраты на её отрисовку больше. И есть ещё один момент: представим основной цикл, мы записываем в VDP (видеопроцессор) адрес в VRAM(видеопамять) по которому мы ходим произвести запись, при последовательной записи этот адрес автоматически увеличивается в VDP, и не приходится его записывать его ещё раз, в случае если адрес случайный, то при каждой записи в VRAM (через VDP) придётся передавать адрес, а это очень затратная операция.
Ну и равномерность потока, так как изображение иногда резко меняется, то важно, что бы очень сильно загруженные моменты видео отрисовывались с такой же скоростью, как и более простые. Поток конечно же не равномерный, в среднем он 120-140 байт, но иногда скачет и до 300 байт на кадр, что бы выровнять этот поток используется буфер в 16кБ, в который подгружается с дискеты по 128 байт в среднем за кадр, так вот буфер на некоторых сценах выбирается на 80%, это когда расходы на отрисовку сцены очень большие и не нет времени вычитывать с дискеты. Мелочей много =) но это интересно =)
horror_x
08.01.2022 16:16+1это очень затратная операция.
Понятно, что в скорости профита бы не было (ибо пограничные случае со 100% разницей всё равно будут), но объём это реально могло бы уменьшить. Если, например, 1-2 байта пропускать дольше, чем писать, то можно применять при повторении 3 или более байт.но нужно также сохранить адрес где произошло изменение
Если есть возможность узнать текущий адрес, то не нужно. Распаковка ведь потоковая. Нужна просто ещё одна команда отрисовки: пропустить N байт (т.е. оставить без изменений).Pyhesty Автор
09.01.2022 02:15никаких операций сравнения, дополнительного суммирования, обращения к памяти в основном цикле выполнять нельзя, так как все очень быстро должно выполняться, несмотря на то, что в статье я написал очень упрощенный алгоритм, основной цикл отображения данных сильно заоптимизирован в борьбе за такты процессора...
однозначно можно и нужно перебирать алгоритмы упаковки и распаковки, возможно они будут лучше, компактнее, в данной реализации после нескольких попыток был оставлен описанный в статье вариант
DolphinSoft
08.01.2022 16:04+1Тогда придется на каждую непоследовательную операцию записи в видеопамять, устанавливать адрес записи через порты, например:
ld a, newAddrHigh
out (#99),a
ld a, #94
out (#99),a
ld a, newAddrLow
out (#99),aДля MSX это 60 тактов и 12 байт на одну запись. Если вдруг найдется кадр с черезбайтовой записью разницы, попадание в частоту кадров будет невыполнимым.
horror_x
08.01.2022 16:20Если вдруг найдется кадр с черезбайтовой записью разницы
Ну обычно такое не кодируется как разница. Во многих реализациях алгоритмов вообще невозможно указать неэффективные значения повторяемых цепочек — диапазон значений смещён (например, на +3: 0000..1111 — не 0..15, а 3..18).Pyhesty Автор
09.01.2022 02:11+1да, смещенный диапазон, к примеру, в распространенном RLE, но в данном случае ориентация была на простоту алгоритма (после переборов нескольких вариантов, которые не давали преимуществ по размеру данных), так как нужно было закодить декодер на Z80, конечно, была реализована модель на более высокоуровневом языке, а потом пренесена на asm z80, сложный алгоритм было бы, возможно, дольше отлаживать.
кстати, получившийся поток сжимается zip с 320кБ, до примерно 260кБ, что говорит о том, что избыточности в нем не так много...
horror_x
09.01.2022 03:46+1кстати, получившийся поток сжимается zip с 320кБ, до примерно 260кБ, что говорит о том, что избыточности в нем не так много...
Ну, я бы на deflate не ориентировался, всё-таки он очень чувствителен к типу данных. Лучше на T6 в данном случае смотреть, он изначально заточен на такое. TIFF вроде его поддерживает.
У меня была парочка проектов по извлечению изображений в формате T6 из БД некоторых приложений. Конвертировал в PNG с deflate. Они в итоге на порядок больше весили.
IgorPie
08.01.2022 17:08+1Я писал в свое время компрессор с потерей качества на zx spectrum
Частотный, по знакоместам 4х4 пиксела (2 байта). Надо сильно ужать видос - оставляешь 16/32/сколько места есть чанков в словаре, и все. Даже 32 чанка уже давали витамин, т.к. 16 бит гарантированно ужимались в 5.
Вероятно, что-то подобное можно сделать для ямы.
axe_chita
08.01.2022 19:45+2MSX forever!
Чистая и талантливая реализация Bad Apple. А главное все олдскульно чисто, без современных костылей при воспроизведении на исходной платформе.
Так сказать непревзойденный pure-изм. ;)
DolphinSoft
Поздравляю с быстрым релизом!
Пускай это будет не последнее творение под MSX, и с нетерпением жду шедевров под MSX2/2+
Успехов! ;)