BACK TO THE PET - дневник разработки (часть 1/2)

Написал и выставил на CAFe 2022 полноформатное демо для редкой в наших краях платформы, одного из древнейших персональных компьютеров — Commodore PET 4032. Монохромный текстовый режим 40x25 без возможности загрузки шрифта, никаких аппаратных скроллов, однобитный бипер на выходе последовательного порта, 32 килобайта ОЗУ, в которые помещается все 4 минуты демо без дозагрузок.



На этот раз я решил не делать традиционный making of, чтобы его написание не затянулось на год после релиза, с мучительными воспоминаниями о подробностях позапозапрошлого проекта. Вместо этого я в очередной раз попытался вести полноценный дневник разработки, записывая мысли непосредственно в рабочем процессе, и в кои-то веки удалось довести документируемый таким образом проект до завершения. Записи до 06.09 сделаны по памяти, до того момента я ещё не определился, что и в каком формате буду делать. Осторожно: плохо структурированное чтиво эпических размеров.



06.08

Посетил ежегодный местный ретро-геймерский конвент GBX Summer Party. Встретил там Шурана, и он сообщил мне, что в этом году снова будет CAFe. Призадумался, а не посетить ли его. Ну а если посещать — то не с пустыми руками, нужно сделать какую-то работу.

13.08

Среди множества заметок о возможных проектах появляется запись идеи реализовать эмулятор PET на внутренних ресурсах ESP8266, без внешней обвязки, для встраивания в клавиатуру. Этой идее несколько месяцев, она периодически приходила в голову до этого.

14.08

У меня накопился с десяток идей для возможных демо, в разных форматах и для разных платформ, включая самую дичь. Часть из них уже была описана в общих чертах. Набросал краткие описания других идей, и попробовал оценить, какую из них можно было бы успеть реализовать за имеющееся время. Точно не помню, но кажется идея демо для PET возникла где-то в этом процессе, изначально её в списке идей не было, видимо в связи с мыслями об эмуляторе PET. Преимущество этой задумки в том, что на создание сложных эффектов нет времени, но на PET их сделать сходу всё равно не получится, за отсутствием графического режима. Значит, можно будет ограничиться относительно простыми псевдографическими эффектами, а основной упор сделать на эстетику платформы. Также есть готовое решение по звуку в виде PeskyTone/PeskySound и плагина для 1tracker, и вообще некоторый относительно недавний опыт — всё это должно позволить уложиться в срок.

15.08

У The 8-bit Guy выходит видео про самодельный корпус для реплики PET. Вероятно это утвердило меня в мысли выбрать именно направление PET.

18.08

Хотя у меня уже есть готовое решение для музыки, оно заточено на минимизацию размера данных в ущерб разнообразию звучания, и процесс сочинения в рамках этой системы относительно трудоёмкий. Хотелось бы иметь возможность использовать для сочинения Reaper — он показал себя очень удобным и экономящим время инструментом в ряде предыдущих проектов, начиная ещё в AONDEMO. Для этого я решил модернизировать мой VSTi плагин PCSPE, а в процессе также решил выделить эту модернизацию в отдельный проект под названием PETCB2. Это то же самое, но с огибающей для формы волны и с другим выходным форматом данных. Изменения были минимальными, и я завершил этот проект за день, включая пример плеера на 6502.

Работа над кодом плеера стала разогревом перед началом работы над демо — я настроил окружение, подготовил конфиг и сборочные файлы, подтянул эмуляторы.

19.08

Написал пакер для выхлопа PETCB2, который позволит сэкономить значительный объём ОЗУ без особых потерь скорости. Демо планируется однозагрузочным, оно должно влезать в 32 килобайта вместе со всеми буферами, потому что PET сразу установил традицию утомительно долгой загрузки на многих компьютерах Commodore. Если на C64 справлялись с этим за счёт кастомных загрузчиков, на PET сделать динамичное демо с подгрузками и непрекращающейся музыкой на нём не представляется возможным, особенно с учётом плохой эмулируемости и недоступности оригинального железа.

На основе кода примера плеера из PETCB2 создал пустой проект для демо. В отличие от многих моих предыдущих проектов, код демо будет писаться полностью вручную на ассемблере, так как производительность PET и объём доступной памяти очень ограничены, компилированный C код тут не прокатит.

21.08

Уже некоторое время набрасывался предварительный список идей для эффектов, новые идеи приходили во время работы, и к этому моменту стало оформляться примерное содержание. Внятного сценария пока нет, работа над ним только начата, но появляется предварительная оценка динамики — порядка трёх минут, чтобы не утомить публику довольно однообразной псевдографикой, и порядка десяти сцен, около 20 секунд на сцену.

Подготовлен тестовый трек такой длительности, чтобы оценить затраты памяти — просто взял трек из AONDEMO, добавил разных форм сигнала для инструментов, экспортировал и упаковал. Получилось 3995 байт.

22.08

Сделал набор символов PETSCII в виде тайлсета для NESST, так как рисовать мне удобнее в нём.

23.08

Начал реализовывать эффект символов Матрицы на ассемблере 6502, и понимаю особенности платформы. Эффекты хочется делать по возможности в 60 FPS, так как они смотрятся значительно интереснее, но на PET с его тактовой частотой 1.0 МГц при 1000 байтах экрана получается порядка 16 тактов на обработку каждого байта экрана за кадр, то есть времени на полную перерисовку экрана впритык. Эффект тормозит.

Прихожу к мысли, что лучше все эффекты прототипировать на C в окружении SDL, и когда алгоритм достаточно оптимизирован, тогда уже переписывать его на ассемблер 6502. Ранее я применял этот подход при создании HEOHDEMO, и он хорошо сработал, это должно сэкономить время. Сделал прототип эффекта Матрицы в таком виде.

24.08

Эффект Матрицы готов и настроен для плавной работы.

05.09

Написал C-прототип задуманного немного ранее эффекта волн на горизонтальных полосках — вариация того, что запомнилось в No Pets Allowed, отладил алгоритм. Начало и конец эффекта, которые представляют собой мини-эффекты, пока отсутствуют. Также нет полного понимания, как именно добиться 60 FPS в этой сцене — только общая идея, но добиться надо.

Сценария всё ещё нет, но список эффектов, которые ближе к практической реализации, постепенно увеличивается. Другие эффекты отпадают после оценки сложности. В частности, была идея эффекта со змейкой, быстро собирающей точки и заполняющей весь экран. После изучения алгоритмов, решающих змейку, выяснилось, что им нужно порядка 60 тысяч шагов для поля 30x30, и настолько эффективный алгоритм реализовать не получится, а заранее просчитанная анимация просто не влезет в ОЗУ.

06.09

Сделал список имён для сцены гритсов, которая пока не придумана, а также список задействованного софта на случай возможных финальных титров.

Задумал простую сцену-филлер с шахматным полем 2x2 символа, где отдельные строки и столбцы будут попиксельно скроллиться относительно других строк и столбцов. Получить в ней 60 FPS должно быть достаточно просто, т.к. нужно просто размножить одни и те же четыре байта из таблицы на две строки или столбца.

Поприкидывал в NESST, выйдет ли сделать ранее задуманную сцену лабиринта — развитие известной в Commodore-кругах простейшей однострочной программы.

Придумал сцену с речевой вставкой, чтобы на экране было некое лицо и оно произносило какую-то фразу. Лицо должно быть в духе страшной рожи из советской аркады Магистраль, а фраза — какой-то слоган Commodore, произносимый речевым синтезатором в духе ранних 80-х. Выбрал известный слоган про Амигу. Прикинул по памяти, и решил, что заморачиваться синтезом из аллофонов нет смысла, трёхсекундная фраза и так должна поместиться в 3-4 килобайта памяти. Перебрал несколько эмуляторов SP0256, Speak&Spell, в итоге нашёл годный VSTSpeek, эмулирующий S.A.M, и что важно, поддерживающий фонетический режим. Сконструировал из фонем фразу, подрезал её нужным образом.

Поискал идеи для рожи с помощью Stable Diffusion, но в итоге пришёл к идее нарисовать сам PET псевдографикой вручную, а лицо представить просто крупными пикселями глаз и рта на экране нарисованного PET. С двух попыток нарисовал этот экран в NESST. Прикинул, как сделать липсинк с технической точки зрения, почитал про раскадровки рта в классической анимации.

Под конец дня подумал, что стоило бы вести дневник разработки. Я уже не раз пытался делать это для разных проектов, но они в итоге не доходили до завершения. С другой стороны, писать making of постфактум довольно утомительно и лень, а детали не задерживаются в памяти, и восстановить процесс прихода к тем или иным решениям иногда уже не получается. Восстановил в памяти и записал предшествующие события.

07.09

Прошло уже некоторое время от начала работ, но на текущий момент фактически готов всего один небольшой эффект. Нет сценария, нет музыки, нет даже названия. Также стало ясно, что для этого проекта эффекты будут идти перед сценарием, то есть буду делать эффекты, которые будут получаться легче, а потом как-то собирать из них демо, вместо того, чтобы запланировать конкретные эффекты и реализовывать их, несмотря на любые затраты времени.

Текущий план действий таков: сосредоточиться на доведении до готовности тех сцен, для которых подготовлено больше всего материала, причём желательно работать над одной сценой за раз, а не над всеми сразу, как это обычно происходит. По готовности нескольких сцен прикинуть намечающийся сценарий — порядок следования готовых элементов; не готовые, но обязательные другие; и возможные новые сцены из списка идей. Тем не менее, обновил файл описания сценария, выписав в него наиболее перспективные эффекты из имеющихся идей. Получилось 15 пунктов, из которых несколько штук — небольшие эффекты для перехода между сцен, а несколько — основные сцены, имеющие свои анимации переходов.

Взялся за голосовую вставку. Вчера подобрал фонетический ввод для VSTSpeek: OHNLIH KAAMOHDOHR PPAEAEAETTT MEYKS IHIHT PAWSIHBLLLLL. Часть с AW звучала некорректно, но начальная гласная мне понравилась больше, и я подрезал итоговый сэмпл для корректного звучания. Тестовый импорт сэмпла я предварительно делал в 1tracker в движке SquatM, новые тесты в BeepFX. Выяснилось, что пропадает звук S и не очень чётко звучат некоторые другие, из-за их смещения относительно нулевого уровня. Пододвинул и сделал громче некоторые места в Wavosaur, и итоговый трёхсекундный сэмпл зазвучал адекватно. Более-менее разборчивый звук получается при размере сэмпла 3.5 килобайт (сжимается apultra до 2.6K), в меньшем качестве теряются свистящие. Высокое качество звука для этой сцены не требуется, главное общий эффект: напомнить, как впечатляли первые голосовые вставки в компьютерных программах. Сделал проигрывание сэмпла, минимальную анимацию рожи и печать фразы снизу во время проигрывания. Фоновая картинка пока не выводится, пока не придумал эффекты для её появления и стирания.

Чтобы уместить все эффекты в имеющейся памяти, нужно сделать их как можно компактнее. Предполагаю задействовать сжатие сцен целиком, вместо со всех их кодом и ресурсами, и распаковку по мере надобности. Сжимать буду apultra. Есть готовый депакер для 6502, но он под другой ассемблер, требуется адаптация. Объём доступной памяти также ограничивает количество эффектов в демо, что с одной стороны неудобно, но с другой позволит удержать объём проекта в рамках приличий, чтобы можно было закончить его вовремя.

08.09

Адаптировал aplib_6502.s к синтаксису CA65. Проверил пока только на картинке с компьютером, но ошибок быть не должно.

Уже некоторое время обдумывал сцену с классическим phong bump mapping. Помимо скорости, основная проблема в том, как отобразить освещение на PET, чтобы это смотрелось интересно. Набросал прототип на C с вариантом, на который делал основную ставку — горизонтальные линии и пиксельные смещения в каждом знакоместе в зависимости от освещённости — результат не смотрится. Возможно, попробую преобразовать эту идею в морфинг выпуклых изображений.

09.09

Провёл эксперимент с прототипом бампа, заменив отображение с линий на распределённые по плотности символы. Смотрится поинтереснее, но SDL-прототип работает на 60 FPS, а на PET хорошо, если получится выжать 30, но скорее будет 20. Для желаемого эффекта нужно обеспечить 256 градаций псевдояркости, тогда как типичная оптимизация для 8-битных платформ подразумевает всего 15 градаций, чтобы уложить основные расчёты в 256-байтную таблицу. В таком виде задача обещает быть сложной, поэтому отложена на потом.

Доделал прототип сцены с волнами, добавил часть с появлением и уезжанием полосок, с быстрым алгоритмом их отрисовки. Теперь требуется переписать её на ассемблер, и похоже, что получить 60 FPS в основной части эффекта будет непросто.

12.09

Продолжение работы после перерыва на выходные. Суммарное количество готовых сцен демо уже некоторое время превышает единицу, но никак не доберётся до уверенной двойки. В связи с этим решил закончить хотя бы сцену с говорящим компьютером.

Для финала этой сцены сделал ещё один микроэффект, гашение экрана псевдояркостью. Вероятно он также пригодится при запуске демо для стирания текущего содержания экрана. Для появления экрана я сначала думал сделать разнонаправленный скролл частей экрана, но это визуально слишком простой эффект. Вместо него решил сделать посимвольную прорисовку картинки. Порядок появления символов я определил вручную, попиксельной анимацией маски в Graphics Gale. Вышло 119 кадров. С помощью очередной вспомогательной программки-конвертера преобразовал анимацию маски в набор данных в формате три байта на символ, адрес и код символа.

Также сделал появление лица на экране несколькими фазами увеличения его из точки в центре до нормального размера.

Для демо пока не написано ни одной ноты музыки. Обычно музыка для демо идёт в начале, а эффекты подгоняются под неё, либо музыка и эффекты делаются одновременно, но на этот раз случай иной, и музыку придётся писать и подгонять под уже готовую визуальную составляющую. Поэтому пока совершенно непонятно, какой она будет. Но ещё с момента оцифровки фразы для сцены говорящего компьютера возникла идея использовать её довольно странно звучащую ритмическую структуру как главный мотив саундтрека. Таким образом, проигрывание фразы в конце демо сыграет роль разрешения музыкальной темы. Пока не уверен, получится ли осуществить эту задумку, но попробовать стоит.

В процессе отладки сцены, после очередного прослушивания фразы, элементы паззла в виде разрозненных идей наконец сложились, и оформилась в общем-то очевидная идея для центральной концепции демо: реклама PET, ретрохайп платформы. Это определило содержание изображений для сцены с морфингом выпуклых фигур, написал её прототип и нашёл более-интересный вариант отображения четырёх картинок с размытием и инверсией выпуклости-впуклости.

13.09

Дальнейшее обдумывание концепции и вариантов названия. Рабочее название PETDEMO слишком простое, но оно неплохо сочетается с названиями моих предыдущих работ. Учитывая направленность демо, есть вариант PET*HYPE. Разумеется, в голову приходили также различные гэги про PETting, но это слабовато. Вполне однозначно ясно, что хотелось бы в том или ином виде включить PET в название.

Возникла одна довольно спорная идея, которая, впрочем, является давней традицией демосцены, и по прежнему не сдаёт позиций — сделать отсылку к такой глыбе поп-культуры, как фильм Назад в будущее, и назвать демо Back to the PET. Совмещая это с идеей хайпа, подразумевается, что это демо могло бы существовать во времена появления платформы, и служить её рекламой. В минусах неоригинальность подхода, традиционные плюсы — расположение более массового зрителя за счёт узнаваемости, элементы для сценария и цитат в контенте. За неимением лучших концепций пока склоняюсь к этой идее.

Начал реализацию сцены морфинга на ассемблере. Сделал сохранение карт высот в формате RLE (три бита высота, пять бит кол-во повторов) и распаковщик на 6502 — смысл этого в экономии памяти под буфера в момент работы эффекта. Также реализовал прочие необходимые элементы, но пока сцена смотрится очень плохо, особенно сама анимация морфинга. Из-за нехватки скорости я попытался использовать оптимизацию в виде обновления экрана по столбцам, но из-за неё происходит мерцание и диагональные разрывы. Придётся переделать, и возможно ограничиться 30 FPS.

14.09

Разобрался со всеми проблемами эффекта морфинга. К сожалению, его плавность оставляет желать лучшего — 20-30 FPS в зависимости от разницы между изображениями. Для избежания сечения с лучом пришлось разделить отрисовку экрана и цикл морфинга, что автоматически снижает скорость эффекта почти вдвое — и даже так едва-едва хватает тактов, чтобы сечения не происходило, и это ещё без музыки. Конечно, можно было бы получить 60 FPS при наличии лишних 8 килобайт ОЗУ для предварительного просчёта всех кадров, но я хочу уложиться в 32 килобайта на всё демо, и это обещает быть довольно непростым делом. Видимо, придётся смириться с недостаточной плавностью. Тайминг самого демо не должен пострадать из-за неравномерной скорости эффекта, так как смена картинок планируется по маркеру в музыке, на каждый удар.

Для завершения сцены морфинга нужен какой-то эффект стирания экрана. Разъезжание или осыпание полосок было бы слишком банальным, поэтому я решил попробовать сделать дополнительный эффект внутри этой сцены: плавное стирание по форме последнего изображения, сердечка. В процессе также пришла идея сделать это со шлейфом символов меньшей плотности. Реализовал эффект следующим образом: нарисовал покадровую анимацию в разрешении 20x25 (так как она симметрична) в Graphics Gale, написал конвертер, выдающий для каждого кадра набор адресов символов для стирания в каждом кадре. Понадобилась одна маленькая хитрость: так как ширина экрана на PET 40 символов, сделать симметричную зарисовку простыми битовыми манипуляциями не получится. Пришлось отдать 10 старших бит слова на хранение вертикального смещения в буфере (Y*40), и младшие 6 бит на горизонтальное смещение. Таким образом легко посчитать адрес символа в обоих половинах экрана. Итоговый эффект смотрится эффектно, хотя и не совсем в стиле сцены. Оставлю пока так.

На доделку даже такой в общем-то примитивной сцены ушёл целый день. Но хотя бы счётчик готовых сцен перевалил за две единицы.

15.09

Среди первых задумок для демо была идея обыграть классическую однострочную программу на Commodore BASIC, рисующую лабиринт случайным чередованием символов / и \. Решил сделать прототип и посмотреть, работает ли изначальная задумка: пустить несколько лабиринтов разного размера в виде слоёв с разной скоростью прокрутки. Оказалось, что задумка не работает, слои сливаются в кашу, трудно отличимую от стандартного лабиринта. Но прототип подсказал другой вариант: сначала рисуем классический лабиринт, потом более крупный и более детализированный. Попробовал сделать версию с тайлами 4x4 символа.

С появлением новых идей и с пониманием, какие эффекты ближе к практической реализации, обновил и уточнил сценарий. Текущая его версия уже более-менее похожа на правду, она содержит десять сцен разной степени сложности. Также есть восемь возможных дополнительных сцен, которые могут быть включены, если удастся сделать их вовремя. Готовность же кода и контента самого демо пока можно оценить только в 10%. Текущая задача прежняя — довести как можно больше сцен до готовности за минимальное время, чтобы как можно раньше появилось то, из чего можно собрать хоть какое-то демо.

Одна из новых сцен, запланированных в сценарии, изначально предполагала просто показ надписи Personal Electronics Transactor, или очень крупной с быстрым горизонтальным скроллом, или же по одному слову, с какими-то простыми эффектами. В процессе поиска идей я случайно нагуглил несколько обложек журнала Commodore Computer Club, на которых часто изображались прохладно одетые красавицы, и это напомнило мне рекламу в компьютерных журналах 70-х — это часто было изображение женщины, взаимодействующей с компьютером — традиция, продолжившаяся и на демосцене с 80-х до наших дней. Так возникла задумка добавить в сцену с надписью три псевдографические картинки, изображающие женские рекламные образы и эффект печати каждого слова — более разнообразный визуальный контент и лишние голоса от мужской аудитории.

Однако, изобразить в монохромном PETSCII 40x25 символов женскую фигуру крайне непросто, а на достижение уровня мастера в этой области времени нет. У меня есть тайл-матчер в NES Screen Tool, и я потратил несколько часов на попытки получить сколько-либо приличную конверсию. Эксперименты показали, что полутоновые фотографии конвертируются крайне плохо, но карандашно-векторные зарисовки, в частности, получаемые через онлайн-стилизаторы, дают более-менее узнаваемый результат. Они всё равно выглядят очень абстрактно-случайно, что стилистически не бьётся с вручную нарисованной сценой говорящего компьютера и, вероятно, с будущим титульным экраном, и я сильно сомневаюсь, что такое сочетание будет смотреться достаточно органично. Впрочем, выбора всё равно нет, обеспечить хорошую стилистически выдержанную PETSCII-псевдографику я сейчас всё равно не смогу.

16.09

Придумал и сделал другой эффект очистки экрана для начала демо — просто прокрутка вверх, которая первые пять строк сдвигает медленно, а потом по строке за кадр. Это нужно, чтобы не начинать демо сразу с хитроумных символьных манипуляций, и повышать интересность визуала постепенно.

Согласно нынешней моде, попробовал сгенерировать подходящие изображения для сцены с Persona Electronics Transactor нейросетью Stable Diffusion, но у неё плохо получается этот сюжет. Раз уж всё равно придётся брать изображения на стороне, решил взять их из той самой рекламы в старых компьютерных журналах. В качестве эффекта для сцены я решил сделать анимацию вращения крупных заглавных букв и печать полных слов обычными символами — традиционная для демосцены композиция 'женщина и говнолёт'. Казалось бы, примитивно, но в рамках платформы даже анимация вращения уже достаточно непростой эффект — её надо убедительно нарисовать псевдографикой, и как-то утрамбовать в память.

Анимацию буквы я начал рисовать в NESST, там есть очень примитивная система переключения между несколькими картами тайлов в пронумерованных файлах. Первые пробы пера показали оптимальный размер буквы — 11 на 10 символов (13 символов в ширину для анимации граней). Экспериментальным путём выяснилось, что нужно 64 фазы вращения, из которых 32 рисуется вручную, и 32 генерируется таблицей отзеркаливания символов. Одна буква занимает 130 байт, 32 кадра — 4 с небольшим килобайта, и таких букв три, плюс ещё 3 килобайта псевдографических картинок. Это почти половина всей доступной памяти, поэтому однозначно потребуется какое-то дополнительное сжатие, помимо сжатия сцены целиком. Даже не хочется думать о последующей сборке всех сцен демо, геморрой обещает быть знатным.

После первых попыток стало понятно, что нарисовать вручную вращение псевдографикой 'на глаз' в принципе можно, но мелкий джиттер значительно портит впечатление, и я решил прибегнуть к старому трюку: смоделировать математически точное вращение в Blender, а потом воссоздать правильные смещения граней в псевдографической анимации вручную. Фактически я сконвертировал анимацию: смоделировал и отрендерил все три буквы в 3D в точном пиксельном размере, строго по сетке, в ортографической проекции, без шейдинга, с разными цветами граней. Моделирование делал по давно придуманному рецепту: создаётся plane, делается subdiv на количество предполагаемых пикселей, далее лишние полигоны-пиксели удаляются, делается extrude. Задаётся точный размер рендера, устанавливается орто камера сверху точно по центру, её масштаб подгоняется до касания plane краёв прямоугольника камеры. Рендер немного доработал руками в Gale, добавив на торец вертикальные линии таким образом, чтобы соответвовать имеющимся символам псевдографики, перевёл в два цвета, и импортировал в NESST тайл-матчингом к набору PETSCII. Получилась реально сконвертированная 3D анимация на PET.

17.09

Определился со способом хранения анимации вращения букв. Все коды символов утрамбовываются в минимальное количество бит — всего 23 символа, значит на один символ хватает 5 бит. Таблица 23-х кодов символов сохраняется, к ней в пару идёт таблица кодов визуально зеркальных символов, для получения программно развёрнутых по горизонтали копий всех кадров. Три оставшиеся бита кодируют 1..8 повторов символа. Стоп-кода нет, так как длина блока данных известна. Буквы кодируются по столбцам. Это позволило утрамбовать 10 килобайт в 3, и они должны неплохо дополнительно сжиматься LZ пакером. Написал конвертер данных, протестировал распаковку в прототипе на SDL. Написал распаковку и вывод анимации для самого PET. Работает на все 60 FPS, что неудивительно.

18.09

Пытаюсь набрать три фоновых картинки для сцены с буквами. К сожалению, результат конверсии в любом случае выглядит сомнительно, а вручную нарисовать не удаётся — как минимум, понадобится много времени. Опробовал ранее возникшую идею обратить эту проблему на пользу сцене — добавить шум к изображению, постоянно обновлять его на экране близкими по плотности пикселей символами. Тогда остаётся общий силуэт, а конкретные детали теряются. Прототип на SDL показал, что обновлять символы можно довольно медленно, а шаг обновления сам по себе даёт интересные визуальные эффекты. Вариант, конечно, тоже так себе, но лучше своей статичной альтернативы.

Посетил второй день Яндекс Демодуляции, где обсудил с несколькими заинтересованными лицами возможность посещения CAFe. В разговорах упоминал о том, что готовлю работу, но не раскрывал никаких деталей. Это напомнило мне, что времени до пати осталось не так уж много, а работы по демо ещё непочатый край.

19.09

Сделал RLE-упаковщик картинок для сцены с буквами, написал скролл картинок и эффект символьного шума на ассемблере для PET. Скроллится только область, в которой есть изображение, поэтому весь эффект работает на 60 FPS.

Набросал алгоритм появления и исчезания слов по буквам в прототипе на SDL. Мне понравилось, как он выглядит при очень небольших значениях максимальной дистанции удаления букв, на 2-3 символа. Хороший вариант для секции с приветствиями.

20.09

Согласно известному принципу 80/20, доделываю те самые рутинные 20 процентов для сцены с буквами, которые растянулись на 80 процентов времени. Сцена в принципе завершена, если не считать сомнительных картинок, и это даёт около трёх завершённых сцен в демо.

21.09

После начальной реализации задуманного в полном объёме, я немного упростил сцену для улучшения динамики: изначально надписи под вращающимися буквами не только посимвольно прилетали, но и улетали, а картинка с женским силуэтом при смене экрана скроллилась обратно в ту же сторону, откуда приехала. Я убрал улетание букв и сделал 60 FPS скролл всех картинок вниз.

Такой эффект перехода между сцен показался мне скучноватым, и я попробовал ещё несколько вариантов. Черезстрочный скролл вниз (сначала одна половина строк, потом другая) работает так же плавно и немного поинтереснее обычного скролла, но всё равно не то. Скролл вниз с одновременным уменьшением плотности пикселей в символах визуально гораздо интереснее, но он не может быть реализован на 60 FPS, и это контрастирует с плавностью в остальных частях сцены. В итоге остановился на варианте скролла по столбцам вверх и вниз, с чередованием через один символ.

Так как сцена состоит из трёх экранов, а для синхронизации с музыкой лучше работают четыре такта, чем три, возникла идея дополнить её ещё одним элементом — показать все три вращающиеся буквы одновременно, уже без фоновой картинки. Однако, скорости работы имеющегося кода не хватает на распаковку трёх букв на скорости 60 FPS. Путём многократного искривления извилин сделал серию оптимизаций в коде распаковки и отрисовки вращающейся буквы, и всё-таки, казалось бы, уложил все три буквы в один кадр. НО только для PAL, то есть в 50 FPS.

Не знаю, по каким причинам, но WinVICE упорно эмулирует только PAL модели, хотя предположительно они были очень редки. Тестирую же я изменения в основном именно в VICE, хотя там и не та модель PET — потому что он не требует набирать вручную RUN при загрузке из PRG-файла и нажимать дополнительные кнопки, и тест можно запустить гораздо быстрее. К тому же у MAME по умолчанию просто гигантское окно, заслоняющее нужные вещи. К сожалению, после теста на NTSC-машине в MAME оказалось, что скорости всё равно не хватило — у PAL-моделей 1000000/50 тактов на кадр, а у NTSC — 1000000/60. При записи дробью разница не так бросается в глаза, но по факту это 20000 против 16666 тактов на кадр, то есть прилично меньше (на 17%). Боюсь, что и в предыдущих сценах с этим будут проблемы. Делать PAL only демо или откатываться до 30 FPS для NTSC не хотелось бы. Пока не знаю, как я буду решать эту проблему. Видимо, придётся как-то урезать осетра.

22.09

Успев расстроиться, взял паузу, а потом сделал ещё один подход к трём буквам. Возникла ещё одна идея для оптимизации, и она выжала недостающие проценты производительности. Правда, теперь немного рвётся экран во время одновременного вращения всех букв, потому что двойной буферизации нет, и буфер под распаковку кадра буквы один, для экономии и памяти и скорости. Но это уже приемлемая шероховатость. Дополнил сцену согласно задумке — прилёт линий, печать надписи. один оборот всех букв одновременно вокруг оси, гашение через уменьшение плотности пикселей в символах. Вопрос подтормаживания остальных элементов этой и других сцен в NTSC ещё предстоит решить.

Решил не делать внутри демо финальные титры, для экономии памяти и структурного разнообразия. Вся информация пойдёт в readme, а краткий кредит я встроил в эффект матрицы — там теперь печатаются надписи, а волны падающих символов их стирают. Наконец-то нашлось подходящее место, чтобы ввернуть пришедшее в голову некоторое время назад словосочетание 'proper demo'. Оно забавно звучит на русском, и этим звучанием иносказательно подразумевает, что попытка выжать из платформы невыжимаемое не очень успешна — такой хулиганский easter egg. Это было очередное небольшое изменение в сценарии, который с предыдущего упоминания уже практически зафинализировался, и получает только такие незначительные дополнения.

Теперь можно сказать, что вполне уверенно готовы четыре сцены из (теперь уже) одиннадцати. Одну сцену из дополнительного списка я решил добавить в обязательном порядке хоть в каком-то виде, чтобы усилить сюжетную линию. Она очень хорошо встаёт в сложившийся сценарий, повышая его связность, тогда как другие идеи из дополнительного списка представляют собой просто рандомные эффекты. В процентах завершённость работы пока всё равно невелика, примерно 20-25. Есть концепция, сценарий, название, часть эффектов и контента. Нет ещё более чем половины сцен, нет музыки и сборки. К вопросу о последней, обдумал запасной план с реализацией подгрузок на вполне вероятный случай невлезания всего демо в одну загрузку. Этот подвешенный вопрос, который прояснится только после завершения всех сцен, отодвигает написание музыки в самый конец очереди задач.

Составил список проблем с торможением и глюками в NTSC. Исправил часть мелких проблем в сцене с вращающимися буками, и заодно изменил чередование столбцов в эффекте убирания экранов на два символа, так смотрится получше.

23.09

Вчера начал понемногу пытаться рисовать титульный экран и графику для сцены таймлайна в начале. За несколько подходов нарисовал различные элементы титульного экрана, которые постепенно собрались в один более-менее приличный вариант, хотя пока и есть сомнения, достаточно ли он крут. Также нужно придумать интересный эффект для его появления, но пока никаких идей на этот счёт не возникло.

Также обдумывал возможную реализацию давно задуманного эффекта отрисовки формы звуковой волны — подобно визуализаторам на YouTube, где для каждого канала чиптюна показывается текущая форма с центровкой по фазе. После ряда итераций сценария в эту сцену также должны отправиться приветствия.

25.09

Работа немного подзастряла, потому что потерян фокус. Есть множество маленьких и больших задач, и пока непонятно, за что хвататься. Разумеется, есть изначальный план по быстрому завершению крупных элементов. Для выхода из затыка использовал традиционный способ: написал список задач, которыми можно было бы заняться в ближайшее время. Нужно устранить торможение вертикального скролла в сцене вращающихся букв, устранить глюк текста на первой надписи в эффекте матрицы, переписать эффект волн на ассемблер (SDL-прототип сделан аж 20 дней назад), сделать прототип эффекта (придуман вчера) для титульного экрана. Как говорится, придерживаться этих планов я, конечно же, не буду — практика показывает, что именно к запланированному часто не лежат руки, но чешутся делать что-то другое.

Впрочем, на этот раз я для разнообразия действительно последовал списку задач. Починил глюк текста, немного ускорил эффект матрицы, ускорил вертикальный скролл — пожертвовав видимостью сечения с лучом, но в этой сцене оно не так заметно, а плавность важнее. Эффект волн разделяется на три части, собственно эффект, а также вход и выход из сцены. Переписал вход и выход на ассемблер, они легко укладываются в 60 FPS, так как в них обновляется совсем небольшая часть экрана.

Набросал прототип для эффекта с клетчатым полем. Пока смотрится совершенно неинтересно, получается ухудшенная версия эффекта волн. Нужно придумать другие паттерны движения.

26.09

Обдумываю реализацию основной части эффекта волн. Занятие удурчающее, цифры не обнадёживают. В этом эффекте надо мало того, что перерисовать весь экран столбцами по 5 тайлов в высоту, но ещё и посчитать 240 (40*6) значений с выборкой из таблицы синуса и умножения на 0..39. Проделать всё это со скоростью 60 раз в секунду наивным алгоритмом PET явно не по плечу. Основная идея, на которую я рассчитываю — генерация процедур для каждой возможной высоты заполнения столбца, причём из пяти байт такая процедура будет записывать в видеопамять только от одного до трёх, так как шаг изменения высоты между соседними столбцами в этой сцене не превышает 8 пикселей. Но похоже, что этого всё равно будет недостаточно. Отрисовка сцены столбцами приведёт к неизбежному сечению с лучом, но здесь выбирать не приходится.

Пока что написал генератор процедур вывода столбцов (программа на C/SDL, выдающая ассемблерный исходник для 6502) и код обвязки из вызова для PET. В простейшем тесте это работает быстро, но, согласно подсчётам, в реальном эффекте понадобится аж 160 процедур вывода столбцов и гораздо больше кода обвязки. Так много процедур нужно, потому что есть два вида столбца — с заполнением от верха и от низа, и также есть полустолбцы сверху и снизу экрана, а клиппинг внутри процедур занял был слишком много времени и места. В любом случае проблемы с этим эффектом ожидаются как по скорости, так и по памяти.

27.09

Изначальный план для эффекта волн предполагал использование таблицы синуса 256 байт со значениями 0..39 и таблицы умножения 40*40. Таблица умножения нужна для получения всех вариантов амплитуд, для плавного начала и завершения движения каждой из полос эффекта. Две таблицы нужны для экономии памяти, за счёт потери в скорости на выборке из таблицы умножения. Это даёт 1856 байт на таблицы, но также в эффекте будет порядка трёх килобайт процедур отрисовки столбцов — многовато.

Решил проэкспериментировать с параметрами эффекта в прототипе на предмет возможности их оптимизации. Уменьшение шагов в таблице синуса заметно ухудшает плавность эффекта. При 64 шагах он становится сильно хуже, чем при 256, очень дёрганый. При 128 потеря плавности видна, но смотрится терпимо. Таблица синуса симметрична с разницей в знаке и порядке значений в четвертях, то есть можно хранить только четверть шагов, а остальные получать из первой. Это позволяет уменьшить размер 256-байтной таблицы до 64 байт, а 128-байтной до 32 байт. Экономия незначительная, но она открывает возможность отказаться от таблицы умножения и хранить сразу нужное количество версий таблицы синуса с заранее отмасштабированными значениями. Если уменьшить количество масштабов с 40 до 20, что конечно заметно, но не так важно — это всего лишь пара секунд от длительности всего эффекта — можно уложить таблицы в 64*20=1280 байт или (с более низким качеством эффекта) в 32*20=640 байт, и заодно избавиться от выборки из второй таблицы. Похоже на план.

Неожиданно возникли проблемы на этапе реализации этого оптимального варианта внутри прототипа. Почему-то нарушается ровность синусоиды, получаемой из четверти, на ней заметна лишняя ступенька, а также сбивается центровка полос. То есть присутствуют некие ошибки в достаточно элементарной математике. Разбираюсь.

28.09

Проблема центровки полос была связана с тем, что столбцы имеют высоту в 5 символов, то есть 40 пикселей, и ноль синусоиды находится на 20 пикселях. Но в верхней части экрана выводится 3 нижних символа от верхней полосы, что сдвигает центр на 4 пикселя ниже. Можно устранить проблему, сдвинув нулевую точку на 4 пикселя выше, но это уменьшит максимальную амплитуду с 20 до 16 пикселей, что вредит внешнему виду эффекта. Был найден компромисс — динамически сдвигать центр с 24 до 20 с увеличением амплитуды. То есть при амплитуде около нуля центр находится на 24 пикселях, а при полном размахе в 20 пикселей — на 20. Это создаёт некоторые проблемы с инверсией фазы при использовании четвертной таблицы синуса, но центр для инверсии можно получить, взяв нулевую амплитуду четверти и умножив её на два. Так как я почти не дружу с математикой и тригонометрией, это объяснение звучит сложнее, чем оно есть — по сути это очень наивный подход. Но он решает проблему.

Дополнительная проблема с центровкой заключалась в неправильной ширине полосы при нулевой амплитуде. Это было связано с тем, что обе версии столбца, с закрашиванием от верха и от низа, были симметричны. То есть при нулевой высоте был закрашен один пиксель, а при максимальной — все. Это также сбивало центровку. Помогло изменение одной из версий на пустой столбец при нулевой высоте.

Вторую проблему, с непонятной плоской ступенькой около нуля, которая была довольно заметна в эффекте (визуально небольшое искажение синусоиды) удалось устранить методом ненаучного тыка — немного сместил начальную фазу синуса при генерации таблицы. Предполагаю, что это был неудачный алиасинг, хотя не уверен.

29.09

Доработал сцену с вращением всех трёх букв PET одновременно — добавил задержку, чтобы они вращались не синхронно, а начинали и заканчивали одна за другой. Так смотрится поинтереснее.

Реализовал всю оставшуюся обвязку эффекта волн на ассемблере 6502 согласно уточнённому при помощи прототипа на SDL оптимальному алгоритму. Сцена полностью работает, и смотрится прикольно, как и было задумано. Но, как я и боялся, она ужасно тормозит, несмотря на максимально развёрнутый код и большое потребление памяти. И похоже, что даже в PAL она работает на три кадра, то есть со скоростью 16 FPS. Придумал небольшую оптимизацию в конверторе столбцов в развёрнутый код, но она ситуацию не изменила.

Эксперименты подтвердили, что значительное торможение происходит в расчёте высоты столбцов — это 240 значений за кадр, и для каждого помимо выборки из таблицы четверти синуса выполняется логика инверсии значений и 16-битное сложение дельты. При таком количестве значений даже банальное прибавление номера столбца, занимающее всего 4 такта, даёт 960 тактов на кадр — около 5% времени кадра.

Так как в таком виде эффект смотрится очень невыгодно, нужна очень крупнокалиберная оптимизация. Возникла идея перейти от развёрнутого кода столбцов вида


        lda #tile		;2
	ldy #0*40		;2
	sta (Z_DST),y	        ;6
	lda #tile		;2
	ldy #1*40		;2
	sta (Z_DST),y	        ;6
	lda #tile		;2
	ldy #2*40		;2
	sta (Z_DST),y	        ;6
	jmp return		;3=33t, 24b (max)

К коду вида

	lda #tile		;2
	sta SCREEN+0*40,y	;5
	lda #tile		;2
	sta SCREEN+0*40,y	;5
	lda #tile		;2
	sta SCREEN+0*40,y	;5
	jmp return		;3=24t, 18b (max)

Он значительно быстрее и немного компактнее. Проблема в том, что с таким кодом понадобится уже не 120, а 240 процедур. И если в худшем случае первый вариант занимал 2880 байт ОЗУ, то новый будет занимать 4320 байт — гораздо больше (фактически цифры меньше, часть опкодов внутри процедур может отсутствовать). Но и работать новый вариант будет почти на четверть быстрее. Впрочем, это только часть от всего времени эффекта, есть ещё обвязка для вызова процедур, которая должна быть выполнена в виде фактического, не развёрнутого цикла (так как адрес возврат из процедуры происходит по фиксированному jmp), и есть вышеупомянутый медленный код расчёта высоты столбцов в строке.

Со всей же обвязкой вывод каждого из 240 столбцов на экране старым кодом занимает до 73 тактов, то есть обвязка занимает больше времени, чем сам вывод. Новый вариант с обвязкой 64 такта. Однако, возможен компромиссный вариант с применением самомодифицирующегося кода для вызова процедур по JSR. На первый взгляд, он только замедлит работу, так как JSR/RTS — это 12 тактов, а JMP/JMP — примерно вдвое меньше. Но вызов по JSR позволяет развернуть цикл обвязки. И тогда с обвязкой процедура вывода столбца будет занимать до 57 тактов, а также все процедуры уменьшатся в размере на два байта, то есть 240 процедур займёт до 3840 байт.

Хотя такая оптимизация и увеличивает расход памяти, она могла бы спасти эффект. Переделал конвертер и код эффекта, но визуально это практически не помогло, так как эффект синхронизирован по обратному ходу луча. Эксперименты показали, что если теперь уменьшить количество вычисляемых высот столбцов в четыре раза, то есть с 240 до 60, то эффект начинает укладываться в один кадр в PAL. То есть код расчёта высоты столбцов нужно ускорить в четыре раза.

30.09

После ряда экспериментов по возможности оптимизации эффекта ситуация изменилась не сильно, но было сделано несколько больших изменений. Я снова отказался от четвертной таблицы синуса с предварительным умножением в пользу двух таблиц, синуса и умножения, но уменьшил диапазон умножений до 20. Код для выборки из четвертей занимает слишком много времени, выборка из таблицы умножения быстрее. Внедрил расчёт высоты только для чётных столбцов, с линейной интерполяцией для нечётных, что значительно ускоряет вычисления и не сильно ухудшает внешний вид эффекта. Интерполяция производится в развёрнутом цикле рендера, так немного эффективнее. Также буфер синусоиды (теперь он имеет размер 21 байт) был перемещён в zeropage и кое-где был применён самомодифицирующийся код, чтобы выиграть ещё несколько тактов.

Ещё одна, с позволения сказать, оптимизация заключается в отказе от синхронизации по vsync для основной части эффекта — это заметно повышает среднюю частоту кадров и визуальную плавность, а рвущийся экран из-за самой природы эффекта совершенно незаметен. Решение сомнительное, но может спасти эффект. Впрочем, не факт, что он в его нынешнем виде влезет в память при итоговой сборке и будет резон его спасать.

Так как идеи по дальнейшей оптимизации эффекта закончились, а времени на него ушло уже очень много, решил оставить его в таком виде до лучших времён и сосредоточиться на других сценах. Для завершения проекта осталось три недели, а надо написать ещё шесть сцен, сочинить музыку и собрать все отдельные элементы в финальный продукт. Похоже, что первый закон Паркинсона снова выполняется — работа занимает всё время, отведённое на неё.

(продолжение следует, весь дневник не влез в Хайп)

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.