Интроспека специально для зхдемору!
Давно хотел попробовать написать новый тип заметки, специально для кодеров zxdemos.ru и лично ААА. Я хочу попробовать поговорить о внутренних циклах дем, о том, на чём держаться эффекты и об идеях, которые позволяют эти эффекты реализовать. Я не буду лезть в подробности реализации и не дам вам дизассемблер для сборки чего-то выдранного. Только идеи. Только самое интересное.
Ну и поскольку мне нужно с чего-то начать, я хочу начать с Illusion от X-Trade. Мне это демо всегда было симпатично.
Первый эффект в демо, это скроллер натянутый на шар. По сути, этот же самый эффект повторён ближе к концу, там где на шар натянута картинка из робокопа:
Итак, как это делается? У нас в памяти есть чёрно-белая картинка, либо скроллер, либо робокоп, но хранится она в виде байтов (байт на пиксель). Байт = 1 означает есть пиксель, байт = 0 — нет пикселя. Это неэкономно по памяти, но позволяет собрать из пикселей новую картинку. В чём идея? Алгоритм рисует сферу горизонтальными полосами вот в таком вот цикле:
Код: DUP 8add a : add (hl)DUP ?inc lEDUPEDUPld (de),a : inc de ; (4+7+4*x)*8+7+6 = 101+32x тактов на байт (в среднем)
В HL — наша исходная картинка (байт на пиксель), в DE — буфер для результата, а в А накапливаются пиксели. Т.е. мы идём слева направо, умножаем А на 2 и добавляем в А текущий пиксель. Теперь фокус, почему я написал DUP? — потому что следующий пиксель может быть тем же, может быть действительно следующим, через 1, или даже, когда мы рисуем самый верх или самый низ сферы, через несколько пикселей от предыдущего. Т.е. программа генерирует такой код для вывода сферы заранее, и кол-во INC L вообще говоря всё время меняется, во время движения по сфере. Самое большое что я нашёл — пропуск 4 пикселей, но я не могу сказал что искал так уж старательно, где-то могло быть пропущено и 5.
Насколько эффективный это вывод? Можно предположить, что у нас в среднем один пропущенный байт. Размер шарика — 56 х 56 пикселей. Тогда выходит, что сфера занимает 56*56/8=392 байт экранной памяти, и поэтому выведется за 133*392 ~ 52136 тактов. Если бы пропущенных байтов было в среднем два, вышло бы (133+32)*392 ~ 64680 тактов, что с учётом вывода на экран, плейера (4.5 тысячи тактов) и задержек памяти, уже не влезало бы во фрейм на классике. Плюс вывод буфера на экран и т.д. Фреймовый эффект.
Второй заинтересовавший меня эффект — это большой поточечный скроллер с прыгающим по нему мячом:
Мячик это конечно спрайт, физика, конечно, ненастоящая. Меня интересовал сам скроллер. Рисуется он в текстуру в памяти, а потом выводится в экран вот таким образом:Код: ld a,(bc) : inc cDUP 8pop hl : rla : jr nc,0fset ?,(hl)0:EDUP ; 10+4+7+15 = 36t на пиксель
В BC — текстура в памяти. Стек настроен на таблицу адресов в экране, куда нужно будет выводить пиксели. Конкретные биты пикселей не меняются (движение батута строго вверх-вниз, движение получено просто сменой таблицы адресов пикселей). Читаем адрес и проверяем, зажжён ли текущий бит, если да — ставим его в экран.
Время вывода одного пикселя — 36 тактов (или меньше, если пиксель погашен), поэтому экран из 1024 пикселей выводится за 1024*36 ~ 36864 тысяч тактов. Заметили, что батут в нижней трети? Значит у нас есть верхний бордер (64 строки на пентагоне), плюс две трети экрана на рисование (ещё 128 строк), т.о. у нас есть до начала отрисовки батута где-то (64+128)*224 = 43008 тактов. Т.е. эффект спокойно успевает в один экран, могли ещё больше точек нарисовать без проблем
Теперь, два главных эффекта в демо, moving shit и ротосоник:
Идея реализации в обоих случаях одна и та же, стандартная для ротаторов и вобблеров. Мы опять хотим текстуру с пикселем на байт. В случае этих двух эффектов у нас пиксели 2х2, ну т.е. всё равно храним в байте #03 если пиксель есть или #00 если пиксель пустой. В начале кадра нам нужно сгенерировать процедуру, которая будет идти по экрану строго горизонтально, слева направо, и, одновременно — по текстуре. По текстуре идти придётся под углом (чтобы получилось вращение) или вообще по какой-нибудь кривульке, чтобы вышло moving shit. Такая процедура в Illusion выглядит вот так:Код: ld a,(hl)inc l : dec h ; эти команды меняются в зависимости как наша линия идёт по текстуреadd a : add a : add (hl)inc l : dec hadd a : add a : add (hl)inc ladd a : add a : add (hl)inc l : dec hld (de),a : inc e ; 7+4+4 + 4+4+7+4+4 + 4+4+7+4+4 + 4+4+7+4+4 + 7+4 = 95t на 4 чанка (один байт)
В HL адрес в текстуре. Чанк 2х2 добавляется в А, а потом мы сдвигаемся по текстуре inc l — влево, dec h — вверх. Конкретные команды пересчитываются каждый фрейм. В DE — адрес буфера в памяти, куда осуществляется вывод. Когда буфер целиком собран, кадр выводится на экран используя стандартный код:Код: pop hl : ld (),hl : ld (),hl ; 10+16+16=42t на 4 экранных байта
Считаем скорость вывода. Размер экрана в Moving Shit — 128 x 96 чанков, которые обсчитываются за 95*(128*96/4) = 291840 тактов. С учётом времени на вывод из буфера и музыки, это 4-5 фреймов на каждый кадр. У соника вывод на весь экран, поэтому скорость на треть медленнее, 5-6 фреймов на кадр.
Один эффект я что-то не понял — zoomscroller в конце. Там несколько кусков кода, который выглядит не так понятно как примеры выше. Если кто-то может рассказать, как он устроен, буду очень благодарен. На этом всё на сегодня, счастливо и попутного кода
