Мультиколор будет побеждён
Первая половина статьи была опубликована в последнем номере замечательного журнала ЗаРулём, а вторая написана по следам свежего релиза нашей новой игры GLUF. Но т.к. они дополняют друг друга я решил их объединить в одну. В ней нет ничего нового для мастеров кода и гуру из 90-х годов, это скорее поток мыслей на тему мультиколора, скрола и попытки их объединения который может быть будет кому-то интересен :)
После того как вышла наша новая игрушка Old Tower мне почти сразу же посыпались просьбы рассказать, как же удалось добиться такой плавной и яркой картинки на нашем старичке Speccy. Ну что же, берите чай и печенки и вперёд!
Прежде всего надо ещё раз напомнить, что же такое мультиколор и с чем его едят. Поэтому я процитирую свою старую статью 2016 года, где я сделал обзор существующих на тот момент мультиколорных движков. «Общеизвестно, что стандартный видеорежим спектрума не позволяет отображать более 2-х цветов в одном знакоместе экрана. Однако существует различные программные трюки, позволяющие обойти это ограничение. Один из них — это так называемый «Мультиколор», или «Rainbow Graphics». Он позволяет, уменьшит высоту знакоместа до блоков 8x1, 8x2, 8х4 с помощью идеальной синхронизации вывода атрибутов и луча, формирующего картинку на экране» Грубо говоря, микросхема ULA, отвечающая за формирование изображения на экране, читает значения цвета из атрибутов не один раз на блок 8 пикселей высотой, а каждую экранную линию, потому нужно только успеть поменять значение в памяти на новые и удача ваша. Это в теории. На практике ширина области мультиколора ограниченна и для блоков 8х1 так и не удалось сделать картинку шире 20 знакомест.
Стоит отметить что ещё в 1988 году в игре Action Force 2 применятся точно такая же технология что и в нашей игре, просто взгляните на картинку, и вы всё поймёте. Почему же они не пошли дальше и не сделали полноценную игру в таком режиме? Увы, нам остаётся только догадываться и сожалеть.
Но постойте, скажет внимательный читатель. Ограничение в ширину и куча цветов — это же идеальная среда чтобы перенести на спектрум какую-нибудь механику с мобилок! Да, именно так я и подумал, и решил, взяв за основу великолепную Tomb Of The Mask перенести её в свои традиционные головоломные уровни. Осталось просто добавить скролинг и дело в шляпе. Но не тут-то было.
Стоит отметить что мультиколор требуют к себе пристального внимания процессора и чем больше высота, тем больше процессорного времени он пожирает. В кадре у нас доступно около 70000 тактов процессора, мультиколор высотой во весь экран требует порядка 43000 тактов. В оставшиеся 27000 тактов нужно уместить скролл пикселей, отрисовку изменившихся тайлов, героя и всю логику игры. Становится понятно, что в один кадр это всё не уместить и нужно как-то разделить задачи на два кадра. Правда мультиколор нужно всё равно «держать» каждый кадр поэтому он сожрёт такты и во втором кадре. К тому же, чтобы он был стабилен, время выполнения программы до того, как луч дойдёт до мультиколорной зоны, должно быть постоянным, т.е. занимать всегда одно и то же количество тактов процессора каждый кадр.
Итак, нам нужно подобрать размер пиксельной и мультиколорной областей так, чтобы они влезали в один кадр, а во второй кадр попытаться впихнуть всё остальное. К тому же время выполнения всех процедур в верхнем бордюре (до того, как луч дойдёт до мультиколора) в обоих кадрах должно быть постоянным. В дополнении ко всему нам надо сделать отрисовку пикселей так, чтобы она не пересекалась с лучом, а для этого времени в верхнем бордюре недостаточно. Голова кругом.
После нескольких зарисовок на бумаге, нескольких часов поисков оптимальных размеров областей и двойной работы по подгонке мультиколора в двух кадрах, у меня получилась такая схема:
Итак, первый кадр. Начнём с момента, когда луч дошёл до места обозначенного зелёной линией. Это самый критичный момент в моём рендере. Как только луч оказался тут мы начинаем формировать новый для кадр нашей игры. Первым делом рисуем 50 нижних линий нашей пиксельной области, на схеме это белый прямоугольник поменьше. После этого у нас остаётся очень-очень мало времени до конца кадра, но нам нужно успеть ещё обновить мультиколор для наших врагов, это единственное место где можно это сделать безопасно и без глюков потому что чуть раньше, в области обозначенной «A» мы их уже отрисовали в пиксельный буфер и прямо сейчас уже нарисовали 50 линий с него.
Второй кадр, сразу же, без долгих раздумий начинаем рисовать 110 оставшихся линий нашего пиксельного буфера (белый прямоугольник побольше) пока луч не добежал до нашей игровой области. После их отрисовки у нас в экранной памяти получается законченная картинка, которая складывается из 50 линий нарисованных в прошлом кадре уже после луча и 110 линий, нарисованных перед лучом, которому ничего не остаётся как нарисовать полный буфер на экран. Так мы избежали подёргиваний и сечений лучом отрисовав линии таким хитрым образом. Количество этих линий рассчитано так что сразу после того как мы их отрисовали начинаются пляски с мультиколором, которые продолжаются всю область, обозначенную буквой «B», причём в обоих кадрах в этой области происходит одно и то же — рисуется мультиколор. А так как область мультиколора всего 12 знакомест шириной, то осталось ещё время нарисовать и стены башни одновременно с мультиколором.
И вот, когда мы попали в область, обозначенную буквой «С», мы попадаем в самое безопасное место в нашем рендере, тут находится вся логика игры, движения врагов, логика пушек, подсчёт очков, словом весь мозг игры. Спокойно занимаемся своими делами до конца кадра.
Снова первый кадр, у нас осталась последняя область в первом кадре обозначенная буквой «А». Как вы помните тут мы можем выполнять код который должен занимать постоянное количество тактов процессора чтобы наш мультиколор не уплыл. Поэтому тут происходит отрисовка крутящихся монеток, врагов, выстрелов и всего того, что можно легко сделать за постоянное количество тактов процессора. Нарисовать что-нибудь с постоянным временем достаточно просто. Например, если нет врага в списке мы просто отрисовываем его в область пзу и.т.д.
Вот так, не вдаваясь в кучу мелких деталей работает рендер в нашей игре. Было ещё очень много других проблем, но это уже совсем другая история.
После релиза Old Tower можно было и остановиться, но хотелось увеличить ширину игрового поля и увеличить размер тайлов и спрайтов. Поэтому я стал думать, как можно развить рендер с прошлой игры в нечто большее. Итак, нужно увеличить ширину мультиколорной области и максимально оптимизировать пиксельный и мультиколорный скроллинги. Так начинался GLUF.
За основу я взял рендер из Old Tower. Первым делом берёмся за мультиколор, чтобы он стал приемлемой ширины пришлось перейти в режим атрибутов 8х2 что тоже весьма красочно, тем более размеры спрайтов и тайлов тоже подрастут до 16х16, так что такой переход не критичен. Здесь не было никаких сложностей, в наше время написать мультиколор 8х2 это достаточно легко, нужно просто немного побегать с лучём наперегонки. Были заморочки с тем чтобы сделать вывод мультиколора из линейного буфера, ведь в таком случае можно его скролить лёгким движением руки, а ещё я немного заморочился и свернул мультиколор в цикл сэкономив приличный кусок памяти. Во время разработки OldTower меня дико раздражало ограничение на количество обновляемых областей мультиколора при восстановлении фона под динамическими элементами, поэтому я решил сделать двойной буфер для мультиколора и обновлять цвета в том буфере, который сейчас не виден на экране. В качестве бонуса получилось сделать эффект заливки красным цветом при смерти героя просто подменив мультиколорный буфер на другой – весь заполненный красным.
А дальше нужно было ускорить вывод пикселей с сохранением возможностью их лёгкого скролинга. После всех экспериментов я остановился на оптимальном размере игровой область в 24*16 знакомест. Отрисовка пикселей это достаточно затратно, поэтому я решил взять самый быстрый метод вывода изображения из буфера в простонародье известный как «лдпуш» он основан на постоянном повторении комбинации команд ld и push. Мы отводим огромный кусок памяти в который генерируем такой код
Это и будет наш пиксельный экран, он статичен. Он просто выводит данные на экран которые в нём же и хранятся. Причем выводит он их задом наперёд, т.к. команда push уменьшает указатель стека, который в нашем случае является адресом куда мы кидаем данные. Таким образом, вместо того чтобы рисовать напрямую в экран мы, будем рисовать прямо в код! Т.е. нам нужно будет каким то образом преобразовывать реальные игровые координаты в адрес нужного нам байта в этом куске кода. К тому же нужно учитывать, что всё будет зеркально, т.к. рисуется задом наперёд и вдобавок ко всему, чтобы двигать горизонтально плавно спрайты шириной 16 пикселей по экрану нам необходимо перерисовывать 3 знакоместа в ширину, а значит вычисление соседнего знакоместе будет совсем не тривиальным. Не стоит забывать, что под спрайтами ещё нужно сохранять и восстанавливать фон. К тому же нужно как-то скролить это всё прыгая на нужную строчку кода и выпрыгивать с неё и ещё обновлять адреса линий куда всё будет выводится. Список проблем рискует перерасти критическую массу, после которой уже опускаются руки.
Но засучив рукава и разбив задачу на более мелкие удалось всё победить. Всё оказалось не столь страшным как казалось изначально.
Начал я с простого, скролинг делается тривиально – начало строки вычислить легко, к тому же каждая «линия» экрана занимает 51 байт кода что облегчает нам все вычисления. Итак, на экране показывается 128 пиксельных линий, точка входа в этот могучий кусок кода вычисляется простым умножением на 51 номера линии, с которой мы хотим вывести картинку, а выход вычисляется прибавлением 128*51. Чтобы сделать выход мы просто патчим код и вклиниваем команду jp (hl) в нужное нам место, не забыв при вызове поместить в hl адрес куда нам нужно вернуться, а после возврата вернуть туда что там было до этого. Всё это старый и всем известный трюк. Потом нам ещё надо обновить все адреса 128-ми линий которые мы будем выводит и дело в шляпе.
Я в принципе не очень хороший кодер, а на z80 и подавно. Поэтому процедуры, которые выводят 5 динамических спрайтов с фиксированным числом тактов в верхнем бордюре второго фрейма и запоминают адреса в «буферах донорах» для восстановления фона дались мне слишком тяжело. И уже сейчас, спустя месяц как я их написал я, смотрю на них и нечего почти там не помню. Поэтому детально описать что там происходит уже не получится. Единственное что могу сказать, что я пожертвовал ещё одним куском памяти в последней быстрой странице чтобы хранить в ней пиксели для восстановления фона. Хранятся они там столбцами и не строками, чтобы максимально быстро их восстанавливать, т.к. вся отрисовка спрайтов построена на выводе столбцами из-за нетривиального перехода к соседнему столбцу.
В конце всей это рутины у меня на саму игру осталось жалкое количество тактов в нижнем бордере второго фрейма. Но тут я уже почувствовал себя в своей тарелке, это уже стало даже привычным, и я даже стал забывать, как это оно, когда у тебя куча тактов. Чтобы стало немножко проще было принято решение забить первый фрейм максимально плотно, а музыку и звуки играть один раз в два фрейма. Переход звука в 25гц благодаря мастерству Олега вышел безболезненным, в музыке появился даже некий шарм.
Вот собственно и всё, дальше было просто дело техники.
Если вы вдруг ещё не играли в наши новые игры, то хочу вам напомнить — скачать и даже поиграть онлайн можно на нашем сайте www.retrosouls.net
Играйте в спектрум, смотрите демки, поддерживайте авторов, пытайтесь сами что-нибудь делать и просто получайте удовольствие от любимого хобби!
Пока!
После того как вышла наша новая игрушка Old Tower мне почти сразу же посыпались просьбы рассказать, как же удалось добиться такой плавной и яркой картинки на нашем старичке Speccy. Ну что же, берите чай и печенки и вперёд!
Прежде всего надо ещё раз напомнить, что же такое мультиколор и с чем его едят. Поэтому я процитирую свою старую статью 2016 года, где я сделал обзор существующих на тот момент мультиколорных движков. «Общеизвестно, что стандартный видеорежим спектрума не позволяет отображать более 2-х цветов в одном знакоместе экрана. Однако существует различные программные трюки, позволяющие обойти это ограничение. Один из них — это так называемый «Мультиколор», или «Rainbow Graphics». Он позволяет, уменьшит высоту знакоместа до блоков 8x1, 8x2, 8х4 с помощью идеальной синхронизации вывода атрибутов и луча, формирующего картинку на экране» Грубо говоря, микросхема ULA, отвечающая за формирование изображения на экране, читает значения цвета из атрибутов не один раз на блок 8 пикселей высотой, а каждую экранную линию, потому нужно только успеть поменять значение в памяти на новые и удача ваша. Это в теории. На практике ширина области мультиколора ограниченна и для блоков 8х1 так и не удалось сделать картинку шире 20 знакомест.
Стоит отметить что ещё в 1988 году в игре Action Force 2 применятся точно такая же технология что и в нашей игре, просто взгляните на картинку, и вы всё поймёте. Почему же они не пошли дальше и не сделали полноценную игру в таком режиме? Увы, нам остаётся только догадываться и сожалеть.
Но постойте, скажет внимательный читатель. Ограничение в ширину и куча цветов — это же идеальная среда чтобы перенести на спектрум какую-нибудь механику с мобилок! Да, именно так я и подумал, и решил, взяв за основу великолепную Tomb Of The Mask перенести её в свои традиционные головоломные уровни. Осталось просто добавить скролинг и дело в шляпе. Но не тут-то было.
Стоит отметить что мультиколор требуют к себе пристального внимания процессора и чем больше высота, тем больше процессорного времени он пожирает. В кадре у нас доступно около 70000 тактов процессора, мультиколор высотой во весь экран требует порядка 43000 тактов. В оставшиеся 27000 тактов нужно уместить скролл пикселей, отрисовку изменившихся тайлов, героя и всю логику игры. Становится понятно, что в один кадр это всё не уместить и нужно как-то разделить задачи на два кадра. Правда мультиколор нужно всё равно «держать» каждый кадр поэтому он сожрёт такты и во втором кадре. К тому же, чтобы он был стабилен, время выполнения программы до того, как луч дойдёт до мультиколорной зоны, должно быть постоянным, т.е. занимать всегда одно и то же количество тактов процессора каждый кадр.
Итак, нам нужно подобрать размер пиксельной и мультиколорной областей так, чтобы они влезали в один кадр, а во второй кадр попытаться впихнуть всё остальное. К тому же время выполнения всех процедур в верхнем бордюре (до того, как луч дойдёт до мультиколора) в обоих кадрах должно быть постоянным. В дополнении ко всему нам надо сделать отрисовку пикселей так, чтобы она не пересекалась с лучом, а для этого времени в верхнем бордюре недостаточно. Голова кругом.
После нескольких зарисовок на бумаге, нескольких часов поисков оптимальных размеров областей и двойной работы по подгонке мультиколора в двух кадрах, у меня получилась такая схема:
Итак, первый кадр. Начнём с момента, когда луч дошёл до места обозначенного зелёной линией. Это самый критичный момент в моём рендере. Как только луч оказался тут мы начинаем формировать новый для кадр нашей игры. Первым делом рисуем 50 нижних линий нашей пиксельной области, на схеме это белый прямоугольник поменьше. После этого у нас остаётся очень-очень мало времени до конца кадра, но нам нужно успеть ещё обновить мультиколор для наших врагов, это единственное место где можно это сделать безопасно и без глюков потому что чуть раньше, в области обозначенной «A» мы их уже отрисовали в пиксельный буфер и прямо сейчас уже нарисовали 50 линий с него.
Второй кадр, сразу же, без долгих раздумий начинаем рисовать 110 оставшихся линий нашего пиксельного буфера (белый прямоугольник побольше) пока луч не добежал до нашей игровой области. После их отрисовки у нас в экранной памяти получается законченная картинка, которая складывается из 50 линий нарисованных в прошлом кадре уже после луча и 110 линий, нарисованных перед лучом, которому ничего не остаётся как нарисовать полный буфер на экран. Так мы избежали подёргиваний и сечений лучом отрисовав линии таким хитрым образом. Количество этих линий рассчитано так что сразу после того как мы их отрисовали начинаются пляски с мультиколором, которые продолжаются всю область, обозначенную буквой «B», причём в обоих кадрах в этой области происходит одно и то же — рисуется мультиколор. А так как область мультиколора всего 12 знакомест шириной, то осталось ещё время нарисовать и стены башни одновременно с мультиколором.
И вот, когда мы попали в область, обозначенную буквой «С», мы попадаем в самое безопасное место в нашем рендере, тут находится вся логика игры, движения врагов, логика пушек, подсчёт очков, словом весь мозг игры. Спокойно занимаемся своими делами до конца кадра.
Снова первый кадр, у нас осталась последняя область в первом кадре обозначенная буквой «А». Как вы помните тут мы можем выполнять код который должен занимать постоянное количество тактов процессора чтобы наш мультиколор не уплыл. Поэтому тут происходит отрисовка крутящихся монеток, врагов, выстрелов и всего того, что можно легко сделать за постоянное количество тактов процессора. Нарисовать что-нибудь с постоянным временем достаточно просто. Например, если нет врага в списке мы просто отрисовываем его в область пзу и.т.д.
Вот так, не вдаваясь в кучу мелких деталей работает рендер в нашей игре. Было ещё очень много других проблем, но это уже совсем другая история.
После релиза Old Tower можно было и остановиться, но хотелось увеличить ширину игрового поля и увеличить размер тайлов и спрайтов. Поэтому я стал думать, как можно развить рендер с прошлой игры в нечто большее. Итак, нужно увеличить ширину мультиколорной области и максимально оптимизировать пиксельный и мультиколорный скроллинги. Так начинался GLUF.
За основу я взял рендер из Old Tower. Первым делом берёмся за мультиколор, чтобы он стал приемлемой ширины пришлось перейти в режим атрибутов 8х2 что тоже весьма красочно, тем более размеры спрайтов и тайлов тоже подрастут до 16х16, так что такой переход не критичен. Здесь не было никаких сложностей, в наше время написать мультиколор 8х2 это достаточно легко, нужно просто немного побегать с лучём наперегонки. Были заморочки с тем чтобы сделать вывод мультиколора из линейного буфера, ведь в таком случае можно его скролить лёгким движением руки, а ещё я немного заморочился и свернул мультиколор в цикл сэкономив приличный кусок памяти. Во время разработки OldTower меня дико раздражало ограничение на количество обновляемых областей мультиколора при восстановлении фона под динамическими элементами, поэтому я решил сделать двойной буфер для мультиколора и обновлять цвета в том буфере, который сейчас не виден на экране. В качестве бонуса получилось сделать эффект заливки красным цветом при смерти героя просто подменив мультиколорный буфер на другой – весь заполненный красным.
А дальше нужно было ускорить вывод пикселей с сохранением возможностью их лёгкого скролинга. После всех экспериментов я остановился на оптимальном размере игровой область в 24*16 знакомест. Отрисовка пикселей это достаточно затратно, поэтому я решил взять самый быстрый метод вывода изображения из буфера в простонародье известный как «лдпуш» он основан на постоянном повторении комбинации команд ld и push. Мы отводим огромный кусок памяти в который генерируем такой код
;у нас будет 320 линий
dup 320
;код для вывода одной линии
ld sp, адрес конца линии экрана
dup 12 : ld de, 2 байта данных : push de : edup
edup
Это и будет наш пиксельный экран, он статичен. Он просто выводит данные на экран которые в нём же и хранятся. Причем выводит он их задом наперёд, т.к. команда push уменьшает указатель стека, который в нашем случае является адресом куда мы кидаем данные. Таким образом, вместо того чтобы рисовать напрямую в экран мы, будем рисовать прямо в код! Т.е. нам нужно будет каким то образом преобразовывать реальные игровые координаты в адрес нужного нам байта в этом куске кода. К тому же нужно учитывать, что всё будет зеркально, т.к. рисуется задом наперёд и вдобавок ко всему, чтобы двигать горизонтально плавно спрайты шириной 16 пикселей по экрану нам необходимо перерисовывать 3 знакоместа в ширину, а значит вычисление соседнего знакоместе будет совсем не тривиальным. Не стоит забывать, что под спрайтами ещё нужно сохранять и восстанавливать фон. К тому же нужно как-то скролить это всё прыгая на нужную строчку кода и выпрыгивать с неё и ещё обновлять адреса линий куда всё будет выводится. Список проблем рискует перерасти критическую массу, после которой уже опускаются руки.
Но засучив рукава и разбив задачу на более мелкие удалось всё победить. Всё оказалось не столь страшным как казалось изначально.
Начал я с простого, скролинг делается тривиально – начало строки вычислить легко, к тому же каждая «линия» экрана занимает 51 байт кода что облегчает нам все вычисления. Итак, на экране показывается 128 пиксельных линий, точка входа в этот могучий кусок кода вычисляется простым умножением на 51 номера линии, с которой мы хотим вывести картинку, а выход вычисляется прибавлением 128*51. Чтобы сделать выход мы просто патчим код и вклиниваем команду jp (hl) в нужное нам место, не забыв при вызове поместить в hl адрес куда нам нужно вернуться, а после возврата вернуть туда что там было до этого. Всё это старый и всем известный трюк. Потом нам ещё надо обновить все адреса 128-ми линий которые мы будем выводит и дело в шляпе.
Я в принципе не очень хороший кодер, а на z80 и подавно. Поэтому процедуры, которые выводят 5 динамических спрайтов с фиксированным числом тактов в верхнем бордюре второго фрейма и запоминают адреса в «буферах донорах» для восстановления фона дались мне слишком тяжело. И уже сейчас, спустя месяц как я их написал я, смотрю на них и нечего почти там не помню. Поэтому детально описать что там происходит уже не получится. Единственное что могу сказать, что я пожертвовал ещё одним куском памяти в последней быстрой странице чтобы хранить в ней пиксели для восстановления фона. Хранятся они там столбцами и не строками, чтобы максимально быстро их восстанавливать, т.к. вся отрисовка спрайтов построена на выводе столбцами из-за нетривиального перехода к соседнему столбцу.
В конце всей это рутины у меня на саму игру осталось жалкое количество тактов в нижнем бордере второго фрейма. Но тут я уже почувствовал себя в своей тарелке, это уже стало даже привычным, и я даже стал забывать, как это оно, когда у тебя куча тактов. Чтобы стало немножко проще было принято решение забить первый фрейм максимально плотно, а музыку и звуки играть один раз в два фрейма. Переход звука в 25гц благодаря мастерству Олега вышел безболезненным, в музыке появился даже некий шарм.
Вот собственно и всё, дальше было просто дело техники.
Если вы вдруг ещё не играли в наши новые игры, то хочу вам напомнить — скачать и даже поиграть онлайн можно на нашем сайте www.retrosouls.net
Играйте в спектрум, смотрите демки, поддерживайте авторов, пытайтесь сами что-нибудь делать и просто получайте удовольствие от любимого хобби!
Пока!
35 комментариев
Про мультиколор 8х1 ты немного отстал: Gasman в Ultraviolet сделал 8х1 мультиколор на 22 знакоместа шириной. Но вообще, очень здорово, что ты написал такую заметку, мало кто понимает как планируется кадр для мультиколорного рендера. А ты объяснил всё очень просто и понятно, может быть это поможет кому-то ещё сделать другие игры и демки с мультиколором, да и не только.
Мне кажется наоборот — уже слишком много людей понимают, как это делается. Чересчур много! Пора бы уже начать распонимать обратно :)
Денис, шапки долой! Замечательная статья!
Вставлять в концы строк jr и округлять до 64 байт это не вариант, т.к. неизбежно потеряется скорость и уменьшиться количество линий которые в 16кб влазят, а там всё впритык и так. В первом фрейме на классике тактов 400 остаётся всего.
Игра достаточно расточительна и съедает почти всю быструю память. Одна быстрая страница целиков под ldpush, во второй килобайт 7 пиксельный буфер для восстановления фона, третья быстрая под четыре мультиколорных буфера, базовый для восстановления цветов под спрайтами, два рабочих которые переключаются реаализуй double buffer и третий залитый красным для эффекта смерти героя. В целом свободно только 6кб из всей быстрой памяти :)
Сейчас чем больше про неё думаю, тем больше нравится — если всё-таки сделать JP NEXT, то можно закольцевать в бесконечную ленту и сделать вертикальный скроллер бесконечным, дорисовывая медленно только одну полоску пикселей на кадр. Интересно еще попробовать всё-таки как то ускорить LD SP,END_OF_LINE, чтобы тоже минимизировать перевычисления в кольце.
Можно ускорить если сделать только один раз ld sp и выводить в «неленейную видеопамять» %) но там другие ньюансы сразу вылезут
но, насколько я понимаю, с техникой ldpush, которая быстро перекидывает на экран теневой буфер, для вертикалкьного скроллинга достаточно иметь всего один избыточный сканлайн, который и будет перерисовываться и два экрана не надо. два экрана это нужно было на тайловых чипах где проворачивание удобно привязать к степени двойки. а у вертикалки+лдпуш+кольцо это не требуется, по крайней мере если не связываться с multycolor, иначе не уверен.
А ведь когда-то я считал, что на спектруме плавный скроллинг физически невозможен.
Перепроверил на Spectaculator — тот показал в два раза примерно меньший фпс. Хм, видимо эмулятор Unreal у меня где то в настройках разогнан, так что всё скромнее на самом деле.
время выполнения обычно меряется бордюрчиком
и получаем синенькую полосочку чтобы визуально оценить скока процедура жрёт от всего кадра :)
Да, наверное просто включен турбо-режим скорпиона, а где он включен даже и не пойму.
В принципе весьма прикольно, регулируя область можно выходить и на плавные скроллеры с 50fps. А если рисовать в бэк-буфер и выводить его каждый второй кадр, то в стабильных 25fps можно наверное наворотить вообще огого.
Дошло, что я это говорю человеку, который как раз воротит «огого!» и не раз и не два и с такими изощрениями, что «огогого!». :D Что-то как то совсем за всеми этими восторгами от техник забыл выразить своё почтение!
Но судя по другим источникам так делают множественные клоны спектрума, а оригинальная модель 48k запаздывала с наступлением прерывания на 16 строк. И даже поэтому в клоны специально апгрейды встраивают в виде схемы задержки наступления прерывания, например тут: zxpress.ru/article.php?id=11847
Странная ситуация когда в разных местах написано разное — а так как multycolor при таких несоответствиях будет просто неправильно работать, то, думаю, не ошибусь если попрошу навести ясность в этой теме.
Оригинальная модель 48К на то и оригинальная, что никуда не опаздывает. У неё действительно прерывание происходит за 64 строки по 224 такта до начала отрисовки экрана. Можно сказать, что верхний бордер у 48К спектрума содержит 64 строки (64*224=14336 машинных тактов). На самом экране строк 192, а на нижнем бордере их ещё 56. Итого 64+192+56 = 312 строк, 312*224=69888 тактов.
128К модели воспроизвели этот расклад только приблизительно, т.к. у них другое число тактов в строке. У серого +2 например на верхнем бордере 63 строки по 228 тактов, а внизу — опять 56, т.е. всего 311 строк, 311*228=70908 тактов на кадр. В итоге верхний бордер почти такой же как на 48К (63*228=14364 тактов, на 28 тактов больше).
А вот на отечественных клонах этот промежуток времени от прерывания до начала отрисовки экрана не выдерживался даже приблизительно. Самый вопиющий пример — Ленинград, у которого кадровое прерывание приходило сразу после отрисовки самой нижней строки экрана, т.е. на Ленинграде нижнего бордера по сути не было вообще, только верхний. На Пентагоне на верхнем бордере фактически 80 строк по 224 такта, т.е. 80*224=17920 тактов, зато на нижнем бордере строк всего 48. В итоге нетривиально рассчитанные эффекты, типа мультиколорного скролла описанного выше Денисом, приходится по сути рассчитывать индивидуально для каждого из клонов.
Код переменного качества от среднего до ужасного, но от этого более понятый новичкам :)