Вход

Просмотр полной версии : Edge Grinder для ZS GMX



izzx
07.07.2021, 13:44
Edge Grinder - игра для Commodore C64,
есть порты на Amstrad CPC 128, Evo Base conf, Evo TS conf.

Решил сделать порт на ZS Scorpion GMX.
Потому что:
- не во что играть
- игра почти полностью подходит по системным требованиям
- можно использовать интересный ход как на Evo: область пикселей заполняется вертикальными полосами,
а потом меняется только область атрибутов, что экономит такты
- интересно посмотреть, как Z80 будет справляться с экраном 16К байт
- интересно проверить свои силы и заодно освоить ассемблер sjasm

Портирование производилось методом правки исходников для Amstrad CPC 128, то есть это порт порта.
Поскольку как программист я не программист, наверное, мой код можно оптимизировать раза в два.
Свои правки я старался отмечать комментариями на русском.

Что получилось:
- Графика сконвертирована в 8 цветов
- Музыка на таблице частот от MSX
- Работает в режиме 7Мгц
- Работает с двумя экранами
- Выжимает 16 кадров/сек
- Скрол побайтно, то есть по два "пикселя" за раз
- Память: слот 0 - основной код и музыка, 1- Загрузчик и доп. код, 2 - Страницы доп. кода и графика, 3- Экраны
- Вся карта перед началом игры отрисовывается в память, для скорости (около 30 страниц, ждать 15 секунд)

Подробнее:
На сайте конкурса есть исходники для Commodore и Amstrad.
http://formatwar.net/view_article.php?art=collabortition_1
Доступны через веб-архив (в архиве выбрать дату 2011.12.06.)

Commodore C64:
- есть исходники
- но незнакомый мне процессор и ассемблер

Evo Base conf:
- похожий экран, но другая структура
- используются все 4 окна памяти (а в GMX есть только 2.5 окна)
- нет исходников

Amstrad CPC 128:
- тот же процессор Z80
- похожий графический режим, но другая структура. Видео режим Amstrad 160*200 точек похож на 640*200 "с полосками" на GMX, но 16 цветов из палитры 27 и есть аппаратный горизонтальный скрол, а в GMX только вертикальный
- похожее переключение банков памяти (но используется в основном окно #4000)
- тот же музыкальный чип AY8912

Подготовка порта на ZS GMX.
Задачи портирования:
- Определить с какой машины проще сделать порт (c Amstrad, есть исходники и похожа архитектура)
- Найти исходники (в веб архиве)
- Определить на каком ассемблере написаны (встроен в эмулятор WinAPE20B2)
- Попробовать собрать и запустить в эмуляторе (успешно, пришлось ещё скачать код плеера)
- Скопировать файлы на ZX (подошёл SteinBlume. CP/M Disk Image Explorer)
- Поправить исходный текст для sjasm

Адаптировать:
- Загрузчик
- Переключение страниц и расположение кода в памяти
- Вывод на экран
- Графику
- Опрос клавиатуры и джоев
- Прерывания
- Плеер музыки

Трудности перевода:
- Так как используется страница 0 вместо ПЗУ, в sjasm не создать снимок SNA для быстрой проверки (или я не знаю как), но можно создать диск TRD и автоматом грузиться с него
- На реальном железе теневой монитор не работает со страницей 0 вместо ПЗУ и с расширенными экранами, поэтому не отладить код
- Нет аппаратного горизонтального скрола как в Amstrad.
Вывод фона 160*78 байт по ldir занимает больше всего времени. Удалось немного ускорить вывод за счёт перехода на ldi.
Но всё равно игра движется медленнее оригинала. Хотя, мне это даже нравится.
Очевидно, можно ещё значительно ускорить.
Но правильнее будет для ZS адаптировать игры с вертикальным скролом.

Спасибо авторам игры и всем причастным, спасибо за исходный код.

Похоже, это первая игра в истории для расширенного экрана ZS!

75796

Игра:
75795

Эмулятор (F3 для выбора дискетки):
https://cloud.mail.ru/public/UbmH/dqVvcRWzm

Исходники и новые версии здесь:
https://cloud.mail.ru/public/XwnA/Wnii3D4Hj

Копия:
https://drive.google.com/drive/folders/19-O-MY2XkgRUmUUZ98xZ83juCuaBe32I?usp=sharing

Видео:

https://youtu.be/ff2MlP-HO5M

Lethargeek
07.07.2021, 14:31
сужу только по скрину, но амстрад удалось превзойти, по крайней мере, по вырвиглазию :D
подобрать погармоничнее сочетания цветов не пытался?
ну там, блики ярко-белым вместо мыльного серого
меньше воспалённой мадженты
объёма больше

izzx
07.07.2021, 14:47
сужу только по скрину, но амстрад удалось превзойти, по крайней мере, по вырвиглазию :D
подобрать погармоничнее сочетания цветов не пытался?
ну там, блики ярко-белым вместо мыльного серого
меньше воспалённой мадженты
объёма больше
Честно говоря, цвета получились немного не такие, как я сам ожидал. Но на оригинал в целом похоже ). Да и мне примелькались. В принципе можно подправить конвертер, только с повышенной яркостью вряд ли выйдет нормально.

Lethargeek
07.07.2021, 16:02
В принципе можно подправить конвертер, только с повышенной яркостью вряд ли выйдет нормально.
так ведь надо "после сборки обработать напильником" (c)

вот пример годного мультиколора спрайтов сайдскроллера:
https://spectrumcomputing.co.uk/entry/30207/ZX-Spectrum/Stormfinch

но уважение за труд в любом случае
это же вообще первая динамичная игрушка для GMX?

izzx
07.07.2021, 16:25
это же вообще первая динамичная игрушка для GMX?
Я думаю вообще первая.

izzx
07.07.2021, 19:11
Теоретически, можно сделать скроллинг по одному пикселю, а не по два. Причём это не скажется на скорости игры. Просто отрисовать карту ещё раз, но со сдвигом. Памяти хватит. Только время загрузки игры увеличится в два раза. Сомневаюсь стоит ли.

Lethargeek
07.07.2021, 20:03
Я думаю вообще первая.
вполне мог быть трэш какой-нить уровня крестоноликов (потому и не дошедший до нас)

Evgeny Muchkin
15.07.2021, 21:22
Сомневаюсь стоит ли.
А почему нет :)


Я думаю вообще первая.

Ес!


"после сборки обработать напильником"

То же самое на ум пришло ;)


погармоничнее сочетания цветов

Да, особенно в интре.


Немного непривычно играть без звуков пальбы и взрывов. Но все равно очень круто!


izzx, Спасибо!

- - - Добавлено - - -

Ура, я 'MEGA HERO'! :)

izzx
16.07.2021, 13:57
Согласен, цвета надо поправить. Хотя бы точнее по отношению к оригиналу. Но только править каждый спрайт нет возможности, это же надо иметь редактор. Да и способности надо иметь. А у меня автоматом всё конвертируется. Только палитру можно менять.



Немного непривычно играть без звуков пальбы и взрывов.

В оригинале без звуков ). Была мысль сделать звуки для GS, но это долгая история.

Ну ещё скрол по пикселям бы сделать.

reddie
16.07.2021, 17:45
А можно запилить видео прохождения (ну, в смысле, работы движка) на ютуб, например? А то без GMX и не посмотреть ))
Кстати, в списке схожих тем (внизу) выдало вот это https://zx-pk.ru/threads/28459-v-razrabotke-nestandartnyj-skroll-shuter-edge-grinder.html
Но там всего один пост с анонсом и на этом все. Хотя, походив по ссылкам, можно понять, что выходила версия под ZX Evo.
А вот для обычного Спека так и не понял, вышла или нет.

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

izzx
16.07.2021, 19:42
А можно запилить видео прохождения (ну, в смысле, работы движка) на ютуб, например? А то без GMX и не посмотреть ))
На счёт видео подумаю. Не знаю пока чем и захватывать. А так, есть же ссылка на эмулятор, запустить не сложно. В эмуляторе, мне показалось, выглядит несколько плавнее, чем на железе, но в целом так же. Если ещё сделаю попиксельный скрол, будет получше.


Кстати, в списке схожих тем (внизу) выдало вот это https://zx-pk.ru/threads/28459-v-razrabotke-nestandartnyj-skroll-shuter-edge-grinder.html
Но там всего один пост с анонсом и на этом все. Хотя, походив по ссылкам, можно понять, что выходила версия под ZX Evo.
А вот для обычного Спека так и не понял, вышла или нет.
Что-то я тоже готовой версии для классики не видал.
Кстати, вопрос про версию для Base conf: там таки есть аппаратный скрол? Немного посмотрел в отладчике, там тактов мало уходит на каждый кадр.


Теперь понял, для чего был вопрос о процедуре перехода вниз на строку GMX =) Полагаю, именно для этой игрухи.
Да ).

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

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

izzx
16.07.2021, 22:24
Если есть желание, можете поругать вот эту часть кода - отрисовка фона, или карты. Занимает больше всего времени. Если использовать команду ldi в 16 тактов, фон размером 160*78 должен копироваться за 199680 тактов. А в режиме турбо всего тактов примерно 123872. Значит, могло бы уложиться в два прерывания. То есть, максимум 25 кадров в секунду где-то в теории. А у меня пока что 16 кадров.
Карта готовая лежит в памяти по 78 столбиков на странице.
Каждый кадр вызывается do_scroll_new для изменения переменных, а затем MapPrint для переброски карты на экран.
Думаю, тут слишком много "обвязки" и есть лишние переменные.


BaseScrAddr equ #c000 ;адрес экрана
RightCol equ #4d ;последний столбик справа
MapLeftPart dw 0 ;число столбцов в левой части экрана для карты
MapRightPart dw 0 ;число столбцов в правой части экрана для карты
PrintMapPag db 0 ;страница памяти с которой начинается карта
PrintMapCol dw 0 ;номер текущего столбика для печати
PrintMapCol2 dw 0 ;количество столбцов для переноса справа
PrintMapAddr dw 0 ;текущий адрес карты

MapPrintTable equ #4000 ;таблица адресов команд копирования 1-78 байт
MapPrintCode equ #4100 ;наборы команд ldi от 1 до 78 подряд, и ret в конце, формируется автоматически при старте, занимает 6240 байт

;печать готовой карты из памяти
MapPrint

ld a,(PrintMapPag) ;включим нужную страницу
call PageSlot2G


ld hl,(PrintMapAddr)
ld bc,(PrintMapCol) ;сколько байт переносить левая часть
; inc c
ld de,BaseScrAddr
ld a,80
sub c
cp 80
jr nc,MapPrint3 ;пропустим если левую часть не надо рисовать
ld (MapRightPart),a
ld a,20*8 ;высота для переноса



MapPrint1
;ldir
push hl ;
ld hl,MapPrintTable-2
add hl,bc
add hl,bc ;теперь известен адрес ссылки на цепочку ldi
ld c,(hl)
inc hl
ld b,(hl)
push bc ;сохраним адрес цепочки
pop ix
pop hl
ld bc,MapPrint11 ;адрес возврата
push bc
jp (ix) ;переход на адрес

MapPrint11
ex de,hl ;пропустим правую часть
ld bc,(MapRightPart)
add hl,bc
ex de,hl
add hl,bc
ld bc,(PrintMapCol)
dec a
jr nz,MapPrint1

MapPrint3
;теперь печать правой части из следующей страницы
ld a,(PrintMapCol)
cp RightCol+1
jr z,MapPrintE ;пропустить если не надо

ld a,(PrintMapPag) ;включим нужную страницу
inc a
call PageSlot2G

ld hl,(PrintMapAddr)
ld de,BaseScrAddr
ld bc,(PrintMapCol)
ld e,c
ld b,0 ;сколько байт переносить правая часть
;ld c,l
ld a,80
sub c
sub 2 ;минус два последних столбика не рисуются
ld c,a
ld (PrintMapCol2),bc
ld a,80
sub l
ld (MapLeftPart),a
ld l,0 ;правая часть карты должна копироваться с начала
ld a,20*8 ;высота для переноса


MapPrint2
;ldir
push hl ;
ld hl,MapPrintTable-2
add hl,bc
add hl,bc ;теперь известен адрес ссылки на цепочку ldi
ld c,(hl)
inc hl
ld b,(hl)
push bc ;сохраним адрес цепочки
pop ix
pop hl
ld bc,MapPrint22 ;адрес возврата
push bc
jp (ix) ;переход на адрес

MapPrint22
ex de,hl ;пропустим левую часть
ld bc,(MapLeftPart)
add hl,bc
ex de,hl
add hl,bc
ld bc,(PrintMapCol2)
dec a
jr nz,MapPrint2


MapPrintE
ret




MapCopyMake ;формирует код быстрого переброса карты, т.е. цепочек ldi
ld ix,MapPrintTable ;таблица переходов
ld hl,MapPrintCode ;формируемый код
ld a,78 ;всего строк
ld c,1 ;начнём с одной ldi
MapCopyMake2
ld b,c
ld (ix),l ;запомнить адрес перехода
inc ix
ld (ix),h
inc ix
MapCopyMake1
ld (hl),#ed
inc hl
ld (hl),#a0 ;код ldi
inc hl
djnz MapCopyMake1
ld (hl),#c9 ;код ret
inc hl
inc c
dec a
jr nz,MapCopyMake2
ret
MapCopyMakeEnd


org #6ac0
do_scroll_new
;рассчёты для отрисовки готовой карты из памяти
;сначала адрес вывода на экран
ld hl,(PrintMapAddr)
inc hl
ld a,l
cp RightCol+1 ;не дошло до края экрана?
jr c,do_scroll_new1
ld l,0
do_scroll_new1
ld (PrintMapAddr),hl
;теперь столбик склейки двух частей левой и правой
ld a,(PrintMapCol)
dec a
jr c,do_scroll_new2
;пора на след.страницу памяти
ld hl,PrintMapPag
inc (hl)
ld a,RightCol+1
do_scroll_new2
ld (PrintMapCol),a
ret


do_scroll_newEnd




Кстати, драйвер памяти для игры взят от TRDN, только не помню чья именно версия, а опрос клавиатуры от DragonsLord.

izzx
17.07.2021, 19:50
Нашёл глюк в своём конвертере графики и разобрался с палитрами. Теперь конвертирует идеально, на мой непросвещённый взгляд.

Оригинал:
https://pic.maxiol.com/thumbs2/1626541041.2990584854.screen.png (https://pic.maxiol.com/?v=1626541041.2990584854.screen.png&dp=2) https://pic.maxiol.com/thumbs2/1626541001.2990584854.222.png (https://pic.maxiol.com/?v=1626541001.2990584854.222.png&dp=2)

Конверсия:
https://pic.maxiol.com/thumbs2/1626541069.2990584854.sshot000000.png (https://pic.maxiol.com/?v=1626541069.2990584854.sshot000000.png&dp=2) https://pic.maxiol.com/thumbs2/1626540527.2990584854.sshot000001.png (https://pic.maxiol.com/?v=1626540527.2990584854.sshot000001.png&dp=2)

Evgeny Muchkin
17.07.2021, 21:59
Супер!

- - - Добавлено - - -

Касательно ГС. Если нужно, могу дать, так сказать, движок. По сути загружалка мода и семплов с их параметрами плюс резидент, упрощающий проигрывание всего загруженного; суть его работы такова, что для запуска какого либо звука, музыки или их остановки, нужно кинуть нужную цифру (команду) в порт #B3. Т.е. проверять служебные биты не требуется, что позволит избежать тормозов. ненужных.

reddie
17.07.2021, 22:19
Насколько понял, восстановление фона под спрайтами не производится, вместо этого перерисовывается весь экран (фон) целиком.
Зато не понял, почему при ширине экрана 80 байт пересылаются только 78. Или так и задумано?
Предположил, что крайний ряд является буфером скроллера, но идет же просто пересылка без сдвигов. Непонятно.
Указано, что карта лежит рядами по 78 байт. А почему так? Она еще и вверх-вниз может скроллиться в игре?
Или откуда эта цифра в 78 байт? Поподробнее о формате хранения куска карты в странице, если можно.

Начнем оптимизировать)) Первое, что сразу бросается в глаза: очень много лишних действий внутри циклов вывода строк.
Скажем, каждый раз вычисляется один и тот же адрес для IX, хотя задавать его нужно всего один раз _перед_ циклом.
Для пар IX/IY обмен с памятью жрет много + тут в цикле берем адрес из таблицы, - огромные потери, около 100 тактов на линию.
Перебрасываем 160 линий, 160х100=16000, если кусками лево-право (работают оба цикла) - уже 32000, это только приблизительно.
Убрав одно лишь задание IX внутри каждого цикла, можно выкинуть кусок кода в красной скобке. Стрелки - команды для оптимизации.

https://i115.fastpic.ru/big/2021/0717/28/4a97133077bc23ddc9e019fd412fef28.gif

Второе: внутри каждого цикла по две команды LD BC,(nn) - очень расточительно в плане затрат времени. Выделено стрелками.
Команды LD BC/DE,(nn) и обратные - LD (nn),BC/DE - жрут на 4 такта и на байт больше памяти, чем такие же с HL,
поэтому везде, где можно, желательно для обмена с памятью использовать пару HL. Это общая рекомендация для любого кода.
Конкретно в этом примере чтение BC еще и вставлено в цикл, причем циклов два (левый и правый столбец) - теряем много тактов.
Напрашивается прямая загрузка в регистры вместо косвенной (из памяти), размещая переменные сразу в коде циклов.

Теперь посмотрим, как избавиться от таблицы переходов по цепочкам LDI, да и от шести килобайт с этими цепочками заодно.
Целых 6 кило места, которого и так вечно не хватает - слишком жирно. Есть способ скользить по коду намного проще.
Размещаем цепочки LDI с максимальной длиной строки (78) прямо в циклах, заодно убрав схему возврата (LD BC,nn:PUSH BC:RET)
и повторную загрузку после них BC из PrintMapCol, которая станет не нужна, т.к. IX будем задавать перед циклом:



dup 78
ldi
edup

dup и edup с числом - директивы для повтора строк кода между ними, могут зависеть от конкретного ассемблера.
Процедур две (для левой и правой части), поэтому цепочек с LDI тоже будет две. 2 по 160 байт всяко лучше, чем 6 кило.
Осталось переделать обе процедуры "прыжка" в нужное нам место, и возврат по RET отпадает за ненадобностью.
Ниже новый листинг. Все исходные переменные в шапке оставил, но MapLeftPart, MapRightPart и PrintMapCol2 не используются.
Можно еще поиграть с размещением переменных из шапки в коде, но они будут вне циклов - на скорости уже не скажется.



BaseScrAddr equ #c000 ;адрес экрана
RightCol equ #4d ;последний столбик справа (#4d=77)
MapLeftPart dw 0 ;число столбцов в левой части экрана для карты - не нужна, переделана в skip_L
MapRightPart dw 0 ;число столбцов в правой части экрана для карты - не нужна, переделана в skip_R
PrintMapPag db 0 ;страница памяти с которой начинается карта
PrintMapCol dw 0 ;номер текущего столбика для печати
PrintMapCol2 dw 0 ;количество столбцов для переноса справа - не нужна, вычисляем IX до цикла
PrintMapAddr dw 0 ;текущий адрес карты

MapPrintTable ;эти две переменные больше не нужны
MapPrintCode ;т.к. убираем таблицу адресов и цепочки LDI

;печать готовой карты из памяти
MapPrint

ld a,(PrintMapPag) ;включаем нужную страницу
call PageSlot2G
ld hl, (PrintMapAddr)
ld de, BaseScrAddr
ld bc, (PrintMapCol) ;ширина левой части
inc c
dec c
jp z,MapPrint3 ;если C=0, левую часть не рисуем. !JP вместо JR
ld a,80
sub c
ld (skip_R+1),a ;ширина пропуска правой части
rlc c ;умножаем на 2 (LDI - 2 байта)
ld ix,MapPr_L+2 ;адрес начала строки LDI'шек
add ix,bc ;получаем в IX адрес прыжка
ld a,160 ;кол-во строк

MapPr_L jp (ix) ;начало цикла левой части

dup 78
ldi ;перенос
edup

skip_R ld bc,0 ;в C загружена ширина пропуска, B=0
ex de,hl
add hl,bc
ex de,hl
add hl,bc
dec a
jp nz,MapPr_L ;JP вместо JR, т.к. команды LDI развернуты в цикле - у JR не хватит смещения


MapPrint3 ;печать (или не печать) правой части из след. страницы

ld a,(PrintMapCol)
cp RightCol+1
jp z,MapPrintE ;!JP вместо JR. А по уму достаточно команды RET Z

ld a,(PrintMapPag) ;включаем нужную страницу
inc a
call PageSlot2G
ld hl,(PrintMapAddr)
ld de,BaseScrAddr ;или ld d,BaseScrAddr/256 (нам нужен только старший байт)
ld bc,(PrintMapCol)
ld e,c
ld b,0
ld a,78 ;сразу отнимаем 2, зачем лишняя команда (sub 2)
sub c
rlca ;умножаем на 2
ld c,a ;смещение для jp (ix)
ld ix,MapPr_R+2
add ix,bc
ld a,80
sub l
ld (skip_L+1),a ;пропуск левой части
ld l,b ;b=0
ld a, 160

MapPr_R jp (ix) ;начало цикла правой части

dup 78
ldi
edup

skip_L ld bc,0
ex de,hl
add hl,bc
ex de,hl
add hl,bc
dec a
jp nz,MapPr_R ;JP вместо JR


MapPrintE ; можно удалить метку и RET, если ниже MapPrint3 поставить RET Z вместо JP Z
ret



Процедура обсчета следующей строки (do_scroll_new) остается без изменений.
Что еще добавить... код не проверялся (набивался в блокноте), возможны глюки =)) Если возникнут вопросы - задавай.
И все равно непонятна фишка с числом 78. Не запуская код и не имея представления о схеме карты. Прошу разъяснить.

PATHNK
17.07.2021, 22:21
Так картинка ужасная.

izzx
17.07.2021, 23:06
Насколько понял, восстановление фона под спрайтами не производится, вместо этого перерисовывается весь экран (фон) целиком.
Да, каждый кадр копируется весь фон, а на него спрайты накладываются. В теневой экран, конечно.

Зато не понял, почему при ширине экрана 80 байт пересылаются только 78. Или так и задумано?
Предположил, что крайний ряд является буфером скроллера, но идет же просто пересылка без сдвигов. Непонятно.
Указано, что карта лежит рядами по 78 байт. А почему так? Она еще и вверх-вниз может скроллиться в игре?
Или откуда эта цифра в 78 байт? Поподробнее о формате хранения куска карты в странице, если можно.
В игре почему-то было 78 столбцов на экране. Последние два пустые всегда.
А карта перед стартом прорисовывается вся как на экран, по 78 столбиков. То есть готовая картинка. Хотя, можно было бы и 80 сделать. Потом всё равно склеивать из двух половин.
Буду изучать ваш код.


Так картинка ужасная.
А какая? Сконвертированная, изначальная или обе?



Касательно ГС. Если нужно, могу дать, так сказать, движок. По сути загружалка мода и семплов с их параметрами плюс резидент, упрощающий проигрывание всего загруженного; суть его работы такова, что для запуска какого либо звука, музыки или их остановки, нужно кинуть нужную цифру (команду) в порт #B3. Т.е. проверять служебные биты не требуется, что позволит избежать тормозов. ненужных.
Когда основное доделаю, надо будет посмотреть.

reddie
17.07.2021, 23:26
В игре почему-то было 78 столбцов на экране. Последние два пустые всегда.
А карта перед стартом прорисовывается вся как на экран, по 78 столбиков. То есть готовая картинка. Хотя, можно было бы и 80 сделать. Потом всё равно склеивать из двух половин.

То есть в каждой банке памяти (16кб) лежит кусок-экран 78х160?
Если скролла карты вверх-вниз нет (только вбок и все), получается 12кб из 16 занято в банке.
Могу предложить переделать формат карты, тогда картинка всегда будет выводиться в одном цикле (без право-лево).
Выигрываем кое-какое время на убийстве пересчета строк одного цикла, примерно 8 тысяч тактов. Вполне ощутимо.
Возможно, несколько возрастет занимаемая картой память, но с учетом 2мб это не проблема, полагаю.

izzx
18.07.2021, 11:07
То есть в каждой банке памяти (16кб) лежит кусок-экран 78х160?
Если скролла карты вверх-вниз нет (только вбок и все), получается 12кб из 16 занято в банке.
Да, кусок карты нарисованный. Скрол только вбок.

Могу предложить переделать формат карты, тогда картинка всегда будет выводиться в одном цикле (без право-лево).
Выигрываем кое-какое время на убийстве пересчета строк одного цикла, примерно 8 тысяч тактов. Вполне ощутимо.
Возможно, несколько возрастет занимаемая картой память, но с учетом 2мб это не проблема, полагаю.

Это может оказаться не просто. У меня сейчас используется родная процедура отрисовки карты. Расчёт координат, потом заполнение буфера шириной 1 байт высотой 160 и переброска буфера на экран. Так рисуется по одному все столбики перед началом игры. Но правда ещё вызывается два раза, потому что в одном байте два виртуальных пикселя.

ld a,(scroll_step)
inc a
ld (scroll_step),a
call ProcessMapPointer
call Fill_Buffer


ld hl,(paint_addr)
inc hl
ld (paint_addr),hl
call Copy_Buffer


P.S. Предложения по карте посмотреть интересно

reddie
18.07.2021, 12:24
Это может оказаться не просто. У меня сейчас используется родная процедура отрисовки карты. Расчёт координат, потом заполнение буфера шириной 1 пиксель и высотой 160 и переброска буфера на экран. Так рисуется по одному все столбики перед началом игры.

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

А есть возможность изменить высоту строящейся пиксельной строки и координаты начала вывода? Суть в чем: для наилучшей переделки вывода под то, что я задумал, нужно разбить вывод экранов напополам, верхнюю и нижнюю часть. Соответственно, в процедуре построения нужно поменять два параметра: высоту строки (80 вместо 160) и текущий адрес вывода, причем мало будет просто задать координаты, - нужно, чтобы процедура понимала, что строит нижнюю часть, а не заново верхнюю половину. Наиболее простой выход - вклиниться в середину процедуры, сделав разбивку на части. Наподобие того, что применено в выводе карты на экран из банков (левая и правая части).

Если не получиться разобраться, могу попробовать сам, благо, исходники для Спектрума в первом посте, как понимаю.

izzx
18.07.2021, 13:28
То есть, насколько понял, карта рисуется поочередно экран за экраном в каждую банку памяти.
Да, так и есть. Рисуется экран, включается следующий банк и так до маркера конца карты.
Я ещё хотел сделать вторую копию карты, смещённую на пиксель. Процедура Fill_Buffer каждый раз смещает правый пиксель налево и рисует справа новый пиксель. Так вот, чтобы в итоге скрол был по пикселям, нужно нарисовать одну версию карты начиная с банка памяти, скажем #10, а каждый второй раз рисовать карту начиная с банка #40. Сейчас карта рисуется 15 секунд, тут будет немного дольше. А если ещё её переконвертить в другой формат, ждать долго ). Можно, конечно, один раз всю карту сделать и грузить потом с диска.

А так нужные процедуры в файле Block_Writer4.asm.
ProcessMapPointer - рассчёт.
В ней переменная scroll_step увеличивается каждый кадр, каждые 16 раз рисуется новый тайл.
Переменная MapPointer указывает на текущую позицию на карте.
Fill_Buffer - запоняет буфер.
Цикл 5 тайлов * 4 знакоместа в высоту * 4*2 пары пикселей знакоместо.
Copy_Buffer - перебрасывает на страницу в #c000.
В HL адрес куда рисовать.

Можно сделать в загрузчике копию этих процедур и ломать как надо. Но надо делать попиксельный скрол.


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

reddie
18.07.2021, 14:09
Процедура Fill_Buffer каждый раз смещает правый пиксель налево и рисует справа новый пиксель. Так вот, чтобы в итоге скрол был по пикселям, нужно нарисовать одну версию карты начиная с банка памяти, скажем #10, а каждый второй раз рисовать карту начиная с банка #40. Сейчас карта рисуется 15 секунд, тут будет немного дольше.
Почти два раза дольше, учитывая сдвиги байтов =) Получается, что улучшенный скролл (карта со сдвигом и без) будет жрать в 2 раза больше памяти. И если правильно въехал, с учетом заполнения "экрана пикселей" матрасом (0F0F), реальное разрешение идет блоками по 4х1 пиксель перерисовкой области атрибутов, а скролл экрана производится сразу целым байтом, т.е. на два "пикселя" 4х1. В принципе, это похоже на познакоместный скролл в обычном экране Спектрума, только тут а ля мультиколор.
Подозреваю, что процедуры сдвига буфера на полбайта и заполнения банков можно здорово ускорить =)
Как там успехи, получается мою процедуру встроить? Глюков не выловлено?

izzx
18.07.2021, 14:45
Почти два раза дольше, учитывая сдвиги байтов =) Получается, что улучшенный скролл (карта со сдвигом и без) будет жрать в 2 раза больше памяти. И если правильно въехал, с учетом заполнения "экрана пикселей" матрасом (0F0F), реальное разрешение идет блоками по 4х1 пиксель перерисовкой области атрибутов, а скролл экрана производится сразу целым байтом, т.е. на два "пикселя" 4х1. В принципе, это похоже на познакоместный скролл в обычном экране Спектрума, только тут а ля мультиколор.
Подозреваю, что процедуры сдвига буфера на полбайта и заполнения банков можно здорово ускорить =)
Всё верно.
Вот такая часть кода, которая сдвигает правый пиксель влево.

LowCharWrite
; this routine shifts the right pixel of a given character byte into the buffer
ld c,7 ;85 ; right pixel mask
ld a,4 ; each character is a column of 8 bytes, handled in pairs
LowChrWrlp
ex af,af' ;1 - save counter
ld a,(hl) ;2 - get right pixel from byte
and a,c ;1 - mask out left pixel
ld b,a ;1 - save result in b
ld a,(de) ;2 - get current byte from buffer
and a,c ;1 - mask out left pixel
rlca ;1 - shift previously right pixel to left
rlca
rlca
or a,b ;1 - add new right pixel to buffer byte
ld (de),a ;2 - write back to buffer
inc h ;1 - point to next byte in character
inc e ;1 - and next byte in buffer

ld a,(hl) ;2 - get byte with next right pixel
rrca ;1 - even bytes stored swapped, so shift left pixel to right
rrca
rrca
and a,c ;1 - and mask out left pixel data
ld b,a ;1 - store result in b
ld a,(de) ;2 - get current byte from buffer
and a,c ;1 - mask out left pixel
rlca ;1 - shift previously right pixel to left
rlca
rlca
or a,b ;1 - add new right pixel to buffer byte
ld (de),a ;2 - write back to buffer
inc h ;1 - point to next byte in character
inc e ;1 - and next byte in buffer

ex af,af' ;1 - get counter back
dec a ;1 - repeat for all 4 byte pairs
jr nz,LowChrWrlp ;2/3 - 33*4-1 = 131 per character, so around 41 scan lines for a whole column
jp FillBufCharRet ; return to main loop


Как там успехи, получается мою процедуру встроить? Глюков не выловлено?
Вот пробую, но пока что сразу перескакивает в конец цепочки ldi. А первый раз должно бы в начало, чтобы сразу 78 байт перекинуть.

reddie
18.07.2021, 15:10
пока что сразу перескакивает в конец цепочки ldi. А первый раз должно бы в начало, чтобы сразу 78 байт перекинуть.
Судя по коду, неправильно обрабатывается переменная PrintMapCol. После компиляции в ней 0, и левая часть (половина) вообще не отрисовывается на первом фрейме.
Но это и в исходном коде так же работает, насколько понял. Там изначально должно лежать 78, а потом в процедуре do_scroll_new уменьшаться до 1 каждый фрейм.
Но после компиляции там лежит ноль. Можно попробовать поменять в переменную в шапке (вписать PrintMapCol dw 78 вместо 0).
Или затыка в другом месте?

izzx
18.07.2021, 15:23
Судя по коду, неправильно обрабатывается переменная PrintMapCol. После компиляции в ней 0, и левая часть (половина) вообще не отрисовывается на первом фрейме.
Но это и в исходном коде так же работает, насколько понял. Там изначально должно лежать 78, а потом в процедуре do_scroll_new уменьшаться до 1 каждый фрейм.
Но после компиляции там лежит ноль. Можно попробовать поменять в переменную в шапке (вписать PrintMapCol dw 78 вместо 0).
Или затыка в другом месте?
Там число RightCol+2 в переменную PrintMapCol потом загружается при старте. Надо просто наоборот сделать, чтобы левая часть сначала рисовалась на полную ширину, а правая 0.
Вот так я переделал пока что:

ld a,(PrintMapPag) ;включаем нужную страницу
call PageSlot2G
ld de, BaseScrAddr
ld bc, (PrintMapCol) ;ширина левой части
inc c
dec c
jp z,MapPrint3 ;если C=0, левую часть не рисуем. !JP вместо JR
ld a,80
sub c
ld (skip_R+1),a ;ширина пропуска правой части
rlc c ;умножаем на 2 (LDI - 2 байта)
ld hl,MapPr_L+78*2+2 ;адрес начала строки LDI'шек
and a
sbc hl,bc ;получаем в IX адрес прыжка
push hl
pop ix
ld hl,(PrintMapAddr)
ld a,160 ;кол-во строк

reddie
18.07.2021, 15:45
Там число RightCol+2 в переменную PrintMapCol потом загружается при старте.
Ах вот оно как, этот момент обговорен не был))


Надо просто наоборот сделать, чтобы левая часть сначала рисовалась на полную ширину, а правая 0.
А если попробовать вот так, чтобы без лишних преобразований:


ld bc, (PrintMapCol) ;ширина левой части
inc c
dec c
jp z,MapPrint3 ;если C=0, левую часть не рисуем. !JP вместо JR
ld a,80
sub c
ld (skip_R+1),a ;ширина пропуска правой части
rlca
ld c,a
ld ix,MapPr_L ;адрес начала строки LDI'шек
add ix,bc ;получаем в IX адрес прыжка

Возможно, будет ошибка на один LDI, тогда скорректировать выражение после MapPr_L (дописать назад +2 либо -2 поставить), и все должно работать.
Я просто не учел логику работы построения адресов таблиц, которая была в изначальном варианте. Там она как бы наоборот сделана, от максимальной к минимальной.
Кстати, левая часть, по идее, рисуется _всегда_, не бывает такого, что выводится только правая. Когда мы в начале экрана - выводим 78 байт целиком, левая=78, правая=0
Когда перебираем смещение, выводим обе части. Как только дошли до "края" - делается переключение страницы и обнуляется сдвиг, правая часть превращается в левую.
Так что эту проверку на ноль в левой части можно выкинуть, она там никогда не должна срабатывать. Если правильно прописана обработка переменных.

izzx
18.07.2021, 15:58
А если попробовать вот так, чтобы без лишних преобразований:

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

reddie
18.07.2021, 16:19
Главное, что есть результат =) Основной задачей было оптимизировать работу циклов, она выполнена.
Но если получится реализовать мою идею с выводом карты в одном цикле, выиграем еще 80+ тактов на линию. 160х80 = 12800, за это стоит побороться.
Как будет время, поковыряю исходники.

izzx
18.07.2021, 19:05
Ещё немного обновил на облаке. На сегодня хватит.
Отрисовка карты раньше занимала 138К тактов, сейчас 113К. В процедуре вроде бы всё по советам подчистил.
Теперь только из-за медленного вывода спрайтов стала видна неравномерная скорость. Когда спрайтов много, замедляется ).

;печать готовой карты из памяти
MapPrint
ld a,(PrintMapPag) ;включаем нужную страницу
call PageSlot2G
ld hl,(PrintMapAddr)
ld de, BaseScrAddr
ld bc, (PrintMapCol) ;ширина левой части
;inc c
;dec c
;jp z,MapPrint3 ;если C=0, левую часть не рисуем. !JP вместо JR
ld a,80
sub c
ld (skip_R+1),a ;ширина пропуска правой части
rlca
ld c,a
ld ix,MapPr_L-2 ;адрес начала строки LDI'шек
add ix,bc ;получаем в IX адрес прыжка
ld a,160 ;кол-во строк


MapPr_L jp (ix) ;начало цикла левой части


dup 78
ldi ;перенос
edup


skip_R ld bc,0 ;в C загружена ширина пропуска, B=0
ex de,hl
add hl,bc
ex de,hl
add hl,bc
dec a
jp nz,MapPr_L ;JP вместо JR, т.к. команды LDI развернуты в цикле - у JR не хватит смещения



MapPrint3 ;печать (или не печать) правой части из след. страницы


ld a,(PrintMapCol)
cp RightCol+1
ret z ;!JP вместо JR. А по уму достаточно команды RET Z


ld a,(PrintMapPag) ;включаем нужную страницу
inc a
call PageSlot2G
ld hl,(PrintMapAddr)
ld de,BaseScrAddr ;или ld d,BaseScrAddr/256 (нам нужен только старший байт)
ld bc,(PrintMapCol)
ld e,c
; ld b,0
; ld a,78 ;сразу отнимаем 2, зачем лишняя команда (sub 2)
; sub c
ld a,c
rlca ;умножаем на 2
ld c,a ;смещение для jp (ix)
ld ix,MapPr_R+2
add ix,bc
ld a,80
sub l
ld (skip_L+1),a ;пропуск левой части
ld l,b ;b=0
ld a, 160


MapPr_R jp (ix) ;начало цикла правой части


dup 78
ldi
edup


skip_L ld bc,0
ex de,hl
add hl,bc
ex de,hl
add hl,bc
dec a
jp nz,MapPr_R ;JP вместо JR
;MapPrintE ; можно удалить метку и RET, если ниже MapPrint3 поставить RET Z вместо JP Z
ret

reddie
18.07.2021, 19:23
Теперь вопрос на засыпку: в первом посте есть ссылка на эмулятор. Он там уже настроен под GMX, сразу можно будет игру запустить?
И второй вопрос: что там за файл 100 мегабайт весом в архиве? Первый раз его вижу, сам эмуль всегда копейки весил.

izzx
18.07.2021, 19:44
Теперь вопрос на засыпку: в первом посте есть ссылка на эмулятор. Он там уже настроен под GMX, сразу можно будет игру запустить?
И второй вопрос: что там за файл 100 мегабайт весом в архиве? Первый раз его вижу, сам эмуль всегда копейки весил.
Да, распаковать и запускать. Только по F3 указать на дискетку TRD. А если сборку прожекта запускать All.bat, то всё автоматом запустится. Но у меня в переменной Path прописан путь на папку Unreal.
А файлик - это образ HDD. Это для Эвы. Можно бы и удалить.

reddie
18.07.2021, 20:06
если сборку прожекта запускать All.bat, то всё автоматом запустится. Но у меня в переменной Path прописан путь на папку Unreal.

Ну это, видимо, нужен SjAsm (или в чем сейчас на PC кодят для Спектрума) - его еще не осваивал. Только реал, только хардкор =))
Unreal более-менее знаю, попробую в нем поковырять через отладчик.

izzx
19.07.2021, 20:23
Сделал скрол по одному пикселю. Выглядит солиднее. И даже время загрузки не увеличилось.
Для зарубежных господ решил осилить гугел диск и выложить копию там.
https://drive.google.com/drive/folders/19-O-MY2XkgRUmUUZ98xZ83juCuaBe32I?usp=sharing

reddie
20.07.2021, 08:13
Сделал скрол по одному пикселю. Выглядит солиднее. И даже время загрузки не увеличилось.

Не просто солиднее, уже играбельно выглядит)) Скролл намного плавнее стал. Плюс скорость скролла замедлилась, не так быстро грохают =))
Кстати, может, зафиксировать фпс на 16-ти? Когда переходит на 25, не успеваешь адекватно реагировать на врагов и стенки.
Делается довольно просто, думаю, и объяснять не нужно, как именно. А высвободившиеся ресурсы можно будет распределить.
Скажем, просчитать, сколько врагов "влезет" в 16 фпс, тогда можно нарастить их число, не парясь о "влезании" в прерывание.
Только что-то надо сделать со скорострельностью, зачастую убивают из-за низкого темпа стрельбы. Можно даже autofire прикрутить, напрягает дрочить кнопку огня.
Сколько теперь банков памяти занимает карта? По идее, вдвое больше, чем при старом варианте.

izzx
20.07.2021, 09:55
Плюс скорость скролла замедлилась,
А кстати не должна бы. У меня раньше два раза рисовалась одна фаза, а теперь просто разные фазы. Чуть усложнил процедуру печати карты, но там копейки, не в цикле.

Кстати, может, зафиксировать фпс на 16-ти? Когда переходит на 25, не успеваешь адекватно реагировать на врагов и стенки.
Делается довольно просто, думаю, и объяснять не нужно, как именно. А высвободившиеся ресурсы можно будет распределить.
Зато веселее, когда внезапные ускорения ). Я ещё надеюсь когда-нибудь ускорить вывод спрайтов. Тогда сгладится немного. И ещё подозреваю, что мой счётчик кадров может привирать.
А как зафиксировать? Считать такты и устраивать паузы?

Скажем, просчитать, сколько врагов "влезет" в 16 фпс, тогда можно нарастить их число, не парясь о "влезании" в прерывание.
Это уже посложнее, чем просто портирование. Не планировал.

Только что-то надо сделать со скорострельностью, зачастую убивают из-за низкого темпа стрельбы. Можно даже autofire прикрутить, напрягает дрочить кнопку огня.
Там пока снаряд не долетит, новый не выстрелит. Надо попадать в такт ). Если будет скорость повыше, то не так заметно.

Сколько теперь банков памяти занимает карта? По идее, вдвое больше, чем при старом варианте.
Да, было 32 банка, теперь ещё 32. У меня, оказывается, и так при построении каждый столбец два раза рисовался. Так что на скорость генерации не повлияло.

reddie
20.07.2021, 10:04
А кстати не должна бы. У меня раньше два раза рисовалась одна фаза, а теперь просто разные фазы.

Не, я про то, что скорость именно перемещения карты стала меньше (от края до края). Не вывода. Но так даже лучше.


А как зафиксировать? Считать такты и устраивать паузы?

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

добавлено: имел в виду не фпс от 3 до 10, а нужное кол-во прерываний на генерацию картинки. Ну, вы поняли (с)

izzx
02.08.2021, 19:42
Удалось на эмуляторе разогнать игру до нормальной скорости, 25 кадр/с.
Выбор не большой: или успевать за 2 прерывания и тогда частота будет 25 кадров, или за 3 прерывания, тогда 16.
Сделал вывод всех спрайтов через цепочки типа ld (hl),NN: inc hl. В версии Amstrad так выводится только ГГ, а у меня уже все, кто не на краю экрана.
Но на железе оно и не подумало разгоняться!
System test 4.30R выдаёт такие скоростя в тактах:
Эмулятор: 71680, turbo 143360
Реальный: 69888, turbo 123872

Получается, мне надо выжать ещё 20к тактов. Подозреваю, что надо опять переделывать скролл и теперь на работу через стек. Если это вообще возможно.

Снова посмотрел на версию для BaseConf – а там оно работает на 14 Мгц и притом активно использует вывод через стек…

Текущую версию положил туда же.
https://cloud.mail.ru/public/o1Fb/X9Mt1Hg14
https://drive.google.com/drive/folders/19-O-MY2XkgRUmUUZ98xZ83juCuaBe32I?usp=sharing

2021.08.01
Ускорен вывод спрайтов
На эмуляторе выдаёт 25 кадров, на реальном железе 16
Добавлен автоогонь (но с ним слишком легко)
Используется 84 страницы памяти из 128

upd. Добавил в первый пост видео своего кривого прохождения.
https://youtu.be/ff2MlP-HO5M

reddie
02.08.2021, 22:51
Сделал вывод всех спрайтов через цепочки типа ld (hl),NN: inc hl

Тогда уж делать через стек, но проработать согласование с прерываниями, дабы не портить спрайты/фон.
В указанном методе на 1 байт уходит 10+6=16 тактов, х на ширину спрайта и добавляем t перевода строки (ADD HL,BC как понимаю)
Через стек спрайты можно выводить как LD DE,nn:PUSH DE, строки переводить ADD HL,BC:LD SP,HL
При спрайте шириной 4 байта уже будет кой-какой выигрыш.

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

izzx
03.08.2021, 10:43
Через стек спрайты можно выводить как LD DE,nn:PUSH DE, строки переводить ADD HL,BC:LD SP,HL
При спрайте шириной 4 байта уже будет кой-какой выигрыш.

Сейчас по данным эмулятора вывод спрайтов редко превышает 10к тактов. И около 5к рассчёт координат. Даже если совсем исключить, то 20к не сэкономить. Но, правда такты не правильно считаются. На самом деле больше.
Но всё равно перспективнее сокращать вывод фона. Там калькулятор говорит около 200к тактов, а эмулятор говорит 115к, что не может быть правдой ). Если выводить через push-pop, то теоретически могло бы быть 160*78*11=137к тактов на реальном железе.

reddie
03.08.2021, 11:28
И около 5к рассчёт координат
Чо-то многовато. Даже без табличных методов должно быть меньше. Сколько спрайтов выводится?

калькулятор говорит около 200к тактов, а эмулятор говорит 115к
Подозреваю, что идет расхождение по сигналу Wait, т.е. в реале он есть, а в эмуляторе его нету.
По уму, должна быть настройка Wait в сетапе эмулятора: на Пентагоне без турбо Wait вообще нет.
На Скорпионах оригинальных (желтом и зеленом) есть, причем разные для этих версий плат.

izzx
03.08.2021, 12:34
Чо-то многовато. Даже без табличных методов должно быть меньше. Сколько спрайтов выводится?
Максимум 8 одновременно. Рассчитывается адрес каждой второй линии. То есть 11 координат на спрайт
Там даже рассчёт идёт с использованием стека и таблиц, но несколько заторможен моими вставками типа sbc hl,de.


По уму, должна быть настройка Wait в сетапе эмулятора

Может что-нибудь я не донастроил. Пока не нашёл таких галок про wait.

reddie
03.08.2021, 21:47
Пока не нашёл таких галок про wait
Там есть настройки "синхронизации" типа размера кадра и прочего, секция ULA



[ULA]
Frame=71680 ; t-states in frame
; Frame=250000 ; t-states in frame - 14MHz
Line=224 ; t-states in line
; Line=784 ; t-states in line - 14MHz
int=50 ; int freq
intstart=13 ; t-states before int
intlen=32 ; int length in t-states
EvenM1=0 ; align M1 cycle to even T-state (for scorpion)
4TBorder=0 ; update border every 4T states (for scorpion)


Это настройки в том эмуляторе из ссылки в теме, синхра под Пентагон.
Ниже лежат пресеты в том числе для Скорпиона, вот там EvenM1=1 - думаю, оно и отвечает за Wait.
Потому что на Скорпах нечетные команды выравнивались до четного значения.

izzx
03.08.2021, 22:13
Там есть настройки "синхронизации" типа размера кадра и прочего, секция ULA



[ULA]
Frame=71680 ; t-states in frame
; Frame=250000 ; t-states in frame - 14MHz
Line=224 ; t-states in line
; Line=784 ; t-states in line - 14MHz
int=50 ; int freq
intstart=13 ; t-states before int
intlen=32 ; int length in t-states
EvenM1=0 ; align M1 cycle to even T-state (for scorpion)
4TBorder=0 ; update border every 4T states (for scorpion)


Это настройки в том эмуляторе из ссылки в теме, синхра под Пентагон.
Ниже лежат пресеты в том числе для Скорпиона, вот там EvenM1=1 - думаю, оно и отвечает за Wait.
Потому что на Скорпах нечетные команды выравнивались до четного значения.
А, вот где собака. Там же можно в окне настроек на закладке ULA выбрать этот пресет. Вот тогда получаются родные 69888 тактов. Но правда в турбе всё равно не то число. Но уже ближе. Осталось с турбой разобраться.

reddie
03.08.2021, 22:44
турбе всё равно не то число. Но уже ближе. Осталось с турбой разобраться
Турба в эмуле, думаю, просто умножает в 2 раза (или сколько накрутить) базовую частоту, это же эмуль.
На реальном компе так не работает. В 2 раза ускоряется только ПЗУ (в идеале), а почти любое чтение/запись ОЗУ
приводит к получению пинка в виде Wait. Причем зависит от конкретной схемы турбо, сколько прилетит.
Скажем, на желтой плате с Альтерой ускорение 1.43 что ли, на зеленой 1.8 (приблизительно, точно не помню).

izzx
04.08.2021, 10:05
Турба в эмуле, думаю, просто умножает в 2 раза (или сколько накрутить) базовую частоту, это же эмуль.
Да, просто умножает. Если забить количество тактов 61936, тогда в турбе правильно показывает ).
На скорпионовском пресете верх изображения в игре стал срываться немного. На реале вроде бы нет такого. Проверю.

reddie
05.08.2021, 15:59
На скорпионовском пресете верх изображения в игре стал срываться немного
Доускорялись, эмулятор глючить начал =))
А сколько всего карта в длину? Т.е. на экран выводится 78 столбцов, а всего их сколько (без учета "двоения" для плавности скролла)?
Хочу прикинуть, сколько памяти сожрет, если все же задействовать мой метод вывода, оптимизировав потребление ресурсов.

izzx
05.08.2021, 17:03
Доускорялись, эмулятор глючить начал =))
А сколько всего карта в длину? Т.е. на экран выводится 78 столбцов, а всего их сколько (без учета "двоения" для плавности скролла)?
Я на реальном посмотрел, картинка нормальная. А в эмуляторе, если поставить параметр "start of int" равным 13, как для пентагона, тогда тоже чисто ). В общем, можно подобрать параметры.

А карта 32 страницы, я прикидывал. Но первая пустая. То есть примерно 31*78=2418 столбцов. Одна фаза.

Но если надо выиграть 20к тактов, то только через стек, я думаю.

reddie
05.08.2021, 18:59
Ну, по грубым прикидкам влазит в 32 страницы, только там хитрый вывод по кускам из разных страниц.
Плюс переписывать построение карты, либо перекодировать из строящейся сейчас. Надо будет глянуть.
Заодно проще будет сделать вывод стеком, т.к. разбивать его на два куска, еще и переменной длины, запарно.
Это в цепочке LDIR рассчитал один раз, куда прыгнуть, и все. А тут процедура на целую страницу...
Хотя, куда ее такую пихать. 0 банк - основной код, 1 банк (#4000) - экран, 2 банк - статичный, 3 банк (#c000) - странички карты.
Или там с адреса #8000 тоже страницы можно щелкать в GMX?

- - - Добавлено - - -

Покурил инструкцию гомикса, вижу, что щелкаются и с #8000, и с #c000. Экранные страницы, как понял, щелкаются на #c000.
Получается, для процедуры вывода нужно разворачивать код в страницу #4000, а ее нынешние данные куда-то перемещать...

izzx
05.08.2021, 19:00
0 банк - основной код, 1 банк (#4000) - экран, 2 банк - статичный, 3 банк (#c000) - странички карты.
Или там с адреса #8000 тоже страницы можно щелкать в GMX?
Память: слот 0 - основной код и музыка, 1- Загрузчик и доп. код, 2 - Страницы доп. кода и графика, 3- Экраны
Да, можно вписывать страницы в окно #8000. У меня щас в окно #8000 вписывается кусок карты и в окно #c000 копируется на экран.
Загрузчик и доп. код с адреса #6200. А до него с адреса #4000 почти пусто. Только пара буферов.

upd. А можно и наоборот экраны в слот 2 включать, а карту в 3й.

reddie
05.08.2021, 19:14
Загрузчик и доп. код с адреса #6200. А до него с адреса #4000 почти пусто. Только пара буферов
Это все может улететь куда-нибудь в другую страницу, чтобы освободить целиком память с #4000 до #8000?
Кстати, почитал старые статьи Зонова про гомикс, там написано, что экраны подключаются взамен основных, с #4000. Нипанятна (с)
https://i115.fastpic.org/big/2021/0805/a9/a7054e0ce193a08700c14ed6dbe8aba9.gif

izzx
05.08.2021, 19:50
Это все может улететь куда-нибудь в другую страницу, чтобы освободить целиком память с #4000 до #8000?
Надо думать. Загрузчик то можно потереть будет. Остальное пока выключить. А потом уже, если заработает вывод фона, то решим куда запихнуть.

Кстати, почитал старые статьи Зонова про гомикс, там написано, что экраны подключаются взамен основных, с #4000. Нипанятна (с)
Видимо взамен тех, что были на #4000. Видео жёстко привязано к страницам 39, 3b,79,7b. Главное в них писать что надо. А в слоте они могут быть и не включены ни в каком.

reddie
05.08.2021, 21:55
Набросал примерный код вывода через стек. Задействованы абсолютно все регистры, зато максимальная скорость.
Выводим группами по 16 байт, 5 групп (последняя - 14 байт, т.к. ширина окна 78).
И выводим линию целиком, а не лево+право, т.к. вставка затычек в код при таком способе сожрет всю экономию.


_SPAD1 LD SP,IY ;первый адрес в линии (откуда берем данные), IY задается перед выводом
POP AF,DE,HL,IX
EXX,EXA
POP AF,BC,DE,HL
LD (_SPAD2+1),SP
LD SP,#C010 ;все адреса назначения задаются в явном виде (при развертывании кода)
PUSH HL,DE,BC,AF
EXX,EXA
PUSH IX,HL,DE,AF
_SPAD2 LD SP,NN
POP AF,DE,HL,IX
EXX,EXA
POP AF,BC,DE,HL
LD (_SPAD3+1),SP
LD SP,#C020
PUSH HL,DE,BC,AF
EXX,EXA
PUSH IX,HL,DE,AF
_SPAD3 LD SP,NN
POP AF,DE,HL,IX
EXX,EXA
POP AF,BC,DE,HL
LD (_SPAD4+1),SP
LD SP,#C030
PUSH HL,DE,BC,AF
EXX,EXA
PUSH IX,HL,DE,AF
_SPAD4 LD SP,NN
POP AF,DE,HL,IX
EXX,EXA
POP AF,BC,DE,HL
LD (_SPAD5+1),SP
LD SP,#C040
PUSH HL,DE,BC,AF
EXX,EXA
PUSH IX,HL,DE,AF
_SPAD5 LD SP,NN
POP DE,HL,IX
EXX
POP AF,BC,DE,HL
LD SP,#C04E
PUSH HL,DE,BC,AF
EXX
PUSH IX,HL,DE

ADD IY,BC ;перебросили линию, пересчитываем адрес данных карты

По времени: каждая группа, кроме последней, жрет 232Т, последняя + ADD IY,BC еще 196 (если без Wait).
Перемножаем: 4х232+196=1124/1164 (с Wait) на линию, 160 линий = 179840/186240 на экран.
Сравниваем с LDI (тоже переделанным): 16х80=1280,+перевод строки еще 22=1232, х160=197120.
Причем LDI команда четная, без Wait. В итоге выигрыш от стека на машине с Wait заметно хуже.
Если точнее (но все равно примерно): разница LDI/стек без Wait около 17 тыс, с Wait около 11 тыс.

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


upd: 1280+22=1302, а не 1232. Считал другой пример, вот и ошибся. Итого 1302х160=208320

izzx
05.08.2021, 22:13
16х80=1280,+перевод строки еще 22=1232, х160=197120.
Вот тут я насчитал 203200. И умножал на 78.


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

reddie
05.08.2021, 22:47
Вот тут я насчитал 203200. И умножал на 78.
Перевод строки 22 такта это LD BC,NN:ADD HL,BC (т.к. копируем 80 байт - DE не трогаем, оно всегда правильное)
При таком варианте и получается указанное у меня число =) Проще копировать 80, чем 78.

Формат примерно тот же, но с нюансами. Сейчас у нас карта разложена экранами по страницам, от края до края экрана.
А можно сделать с перекрытием экранов. Что увеличит расход памяти, но избавит от вывода по половинкам.
То есть в первой странице лежит (пока пишу условно) карта с 1 по 200 столбик, а во второй не с 201, а со 122 (201-ширина)
Получается что: когда в 1-й странице доходим до столбика 121 - "упираемся" в край страницы.
Дальше вместо склеивания страниц просто переключаемся на следующую и выводим со 122 столбика.
Надеюсь, суть понятна: края экранов перекрывают друг друга в соседних страницах.
Проблема в том, что нужно "удлинять" экраны путем дробления их по горизонтали на 2 или даже 4 части.
Потому что при полной высоте экрана (160) получаем в странице 16384/160=102 столбца. А у нас ширина окна 80.
То бишь при таком хранении будет огромный расход памяти на перекрытие краев.
Можно разбить карту на горизонтальные куски, скажем, 4. Тогда в страницу влезет уже 16384/40=409 столбцов.
Соответственно, распухание карты составит примерно 1/5 от исходного размера (409/80 будет 5 если грубо).
Это уже вполне терпимо, и даже при удвоенном расходе памяти на "попиксельный" скролл ее хватит.
Надеюсь, суть понятна. Остается переписать генерацию карты под такой формат выкладки =)

- - - Добавлено - - -

Да, там при подсчете с LDIR обсчитался по запаре, получается больше моих 197 тысяч. Бывает...

izzx
06.08.2021, 12:03
Можно разбить карту на горизонтальные куски, скажем, 4.
Это хорошо. Вывести сначала 3 куска, а потом ждать прерывания. Иначе же оно попортит часть карты.
Выводящий код достаточно хранить для одного куска, как я понимаю. Иначе код не влезет в одну страницу.

reddie
06.08.2021, 12:47
Вывести сначала 3 куска, а потом ждать прерывания. Иначе же оно попортит часть карты
Да, про этот нюанс и хотел сказать. Что при выводе через стек придется мудрить с прерываниями.

достаточно хранить для одного куска, как я понимаю. Иначе код не влезет в одну страницу
Нет, если адреса задаются в явном виде - разворачивается код целиком для всего экрана (окна вывода).
Вроде должен влазить в 16к, если через push-pop, там же на 2 байта данных 2 байта (push+pop) и немного обвязки.

izzx
06.08.2021, 13:35
Нет, если адреса задаются в явном виде - разворачивается код целиком для всего экрана (окна вывода).
Да, точно.

Вроде должен влазить в 16к, если через push-pop, там же на 2 байта данных 2 байта (push+pop) и немного обвязки.

Да. Там же и не весь экран надо выводить. Должно влезть.

upd. Эмуль у меня такты делит на два ). Может я опять что-то не донастроил. Но про команду ldi говорит 8 тактов. И все какие посмотрел делит на 2. Получается вывод фона сейчас 115*2=230к тактов. Можно попытаться выиграть заветные 20. Но если всего 123*2=246к тактов, то надо сильно постараться. У меня пока вся игра укладывалась в эмуляторные 286 тактов, так что на самом деле надо выжать 40к...

reddie
06.08.2021, 20:04
Получается вывод фона сейчас 115*2=230к тактов
Ну это и так вполне было понятно =) умножаем 78х160х16 получаем уже 200 тысяч, плюс обвязка двух циклов.
Вывод по стеку, кстати, никак не влазит в 16Кб, хоть push-pop, хоть pop-ld (nn). У первого много обвязки, у второго размер данных х2.
Окно вывода 78х160=12480х2 = почти 25 тысяч через второй способ. Через первый мм чуть поменьше.
Можно сделать оба адреса пересчитываемыми в коде (откуда + куда), но жрать станет больше по времени, по сути, сравняется с LDI.
Остается хардкорный вариант раскидать музу и весь остальной код по страницам, оставив в нулевой лишь блок управления =)

izzx
06.08.2021, 21:05
Остается хардкорный вариант раскидать музу и весь остальной код по страницам, оставив в нулевой лишь блок управления =)
Музыку не просто перенести. Она скомпилирована с кучей абсолютных адресов. Хотя тут есть спецы по музыке с амстрад, они наверно что хошь перенесут. Можно пока что музыку тоже выключить. Посмотреть что получится. Я прикинул что надо делить на 5 частей, потому что фон состоит из пяти тайлов. Легче будет растянуть в полоски. И 3/5 вывода должно почти ровно в прерывание влезать.

reddie
06.08.2021, 23:11
Музыку не просто перенести. Она скомпилирована с кучей абсолютных адресов
Там даже не в амстраде суть, а в перелопачивании всех адресов вручную, на крайняк.
Если в коде нет вычисляемых выражениями адресов - это муторно, но вполне осуществимо.


3/5 вывода должно почти ровно в прерывание влезать
Ну, если смириться с двухкратным размером кода переброса экрана, можно и с прерываниями выводить.
Конструкциями вида POP HL:LD (NN),HL - так и код проще развернуть будет, и стек обработать.
Процедура обработки прерывания сверяет адрес стека (или кода), если идет переброс -
достаточно восстановить всего 2 байта под стеком, причем мы знаем, каких - это HL.
Примерно так оно будет выглядеть:


INTERR LD (CURRSP+1),SP
LD SP,INTBUF+100
PUSH AF,BC,DE,HL... ;сохраняем все изменяемые регистры
BLABLA ;процедуры на прерываниях
......
POP ...HL,DE,BC ;восстанавливаем, кроме AF
LD A,(CURRSP+2) ;старший байт стека
RLA ;если SP>=#8000 - идет переброс
JR C,CURRSP-1
POP AF ;иначе нормальный выход
LD SP,(CURRSP+1)
EI
RET

POP AF
CURRSP LD SP,0
POP HL ;адрес возврата в процедуру вывода
LD (RET_JP+1),HL
LD HL,(INTBUF+92) ;адрес хранения пары HL
PUSH HL ;востанавливаем затертых 2 байта
POP HL
EI
RET_JP JP 0

INTBUF DEFS 100 ;буфер с запасом под временный стек


Буфер должен быть именно с запасом, т.к. музыка, переключение страниц и прочее.

izzx
12.08.2021, 12:00
Освободил для нового кода печати фона 25К.

Теперь надо перенести музыку на другие адреса. Кто поможет?
WON4.C - адрес #26d7, перенести на #A800
EDGEA.C - адрес #29c3, перенести на адрес #AB00
Здесь в любом архиве валяются
https://cloud.mail.ru/public/o1Fb/X9Mt1Hg14
https://drive.google.com/drive/folders/19-O-MY2XkgRUmUUZ98xZ83juCuaBe32I?usp=sharing

Музыка для ArkosTrackerPlayer_CPC_MSX. На сайте пишут сложно перенести:
How to relocate songs
Relocating a song is complicated. All data values inside the song point to absolute memory locations, and the format of the Instrument is not that simple. The easiest way to relocate a song is to open it in Arkos Tracker and export it again to the desired new memory location address.
https://lronaldo.github.io/cpctelera/files/audio/arkosplayer-txt.html#Can_I_remove_parts_of_the_code_if_I_need_ memory

А плеер для винды можно скачать здесь
http://www.julien-nevo.com/arkos/arkostracker1/
Но он не хочет открывать bin файлы.


upd. Отмена, всё нашлось! Оказывается, есть исходники в формате *.sks

izzx
15.08.2021, 20:24
Ну, если смириться с двухкратным размером кода переброса экрана, можно и с прерываниями выводить.
Конструкциями вида POP HL:LD (NN),HL - так и код проще развернуть будет, и стек обработать.
Процедура обработки прерывания сверяет адрес стека (или кода), если идет переброс -
достаточно восстановить всего 2 байта под стеком, причем мы знаем, каких - это HL.

Почти заработало на железе со скоростью 25 кадров, с выводом фона через POP HL:LD (NN),HL. Но, правда, иногда виснет ).
Пока ищу в чём причина, но тут я подумал, что может иногда портится и фон. Это к зависаниям не приведёт, но всё же.
Вот в таком месте должна пострадать карта:

pop hl
ld (NN), hl
pop hl
ld (NN), hl
add iy,de
ld sp,iy
<прерывание>
pop hl
ld (NN), hl

Тут стек уже будет в другом месте, а значение HL со старого адреса.

reddie
15.08.2021, 22:01
Тут стек уже будет в другом месте, а значение HL со старого адреса

Щас переправим обработчик =)) Кстати, вместо IY может быть и IX, не принципиально. Обычно IY не трогают, если как-либо используется код ПЗУ, но в нашем случае, полагаю, пофиг.
Тэкс, переписал обработчик, но есть важное замечание: аккум при перебросе не задействовать. Вернее, перед любым действием с парой AF нужно восстанавливать стек, а это вне переброса.
То есть переключение страничек при перебросе и все остальное, на что влияет AF, должно идти с "нормальным" стеком.
Объясню, в чем суть: проверяется SP, если он >#8000 - идет переброс. После чего проверяется код команды после выхода из обработчика, а делается это через аккум и флаги, как иначе-то.
Если сохранять аккум, придется городить тройное восстановление SP из-за ветвления, а так просто обходим PUSH-POP HL и все.
Попутно стала ненужной команда POP AF, из-за чего переход делается сразу на CURRSP, если идет переброс.


INTERR LD (CURRSP+1),SP
LD SP,INTBUF+100
PUSH AF,BC,DE,HL... ;сохраняем все изменяемые регистры
BLABLA ;процедуры на прерываниях
......
POP ...HL,DE,BC ;восстанавливаем, кроме AF
LD A,(CURRSP+2) ;старший байт стека
RLA ;если SP>=#8000 - идет переброс
JR C,CURRSP
POP AF ;иначе нормальный выход
LD SP,(CURRSP+1)
EI
RET

CURRSP LD SP,0 ;пару AF в коде вывода не используем!
POP HL ;адрес возврата в процедуру вывода
LD (RET_JP+1),HL
LD A,(HL) ;проверяем код следующей команды
CP #E1 ;код POP (HL)
LD HL,(INTBUF+92) ;восстанавливаем HL
JR Z,$+4 ;совпало - переход сразу на EI
PUSH HL ;востанавливаем затертых 2 байта
POP HL
EI
RET_JP JP 0

INTBUF DEFS 100 ;буфер стека


Вроде ничего не упустил, но мало ли =) А с зависаниями проще разобраться в эмуляторе через отладчик, где оно там виснет.
Можно и через теневик на реале попробовать, но эмулятор стек не портит.

upd: не, так не будет работать. Нужно проверять кое-что другое, айн момент.

еще upd: нда, с перестановкой стека запара. Вернее, с индексным регистром. По идее, можно восстановить через еще один регистр,
но команды обмена с IX/IY жрут больше всего тактов... потеряется весь смысл перехода на стек. Надо еще подумать, но это уже не сегодня.
Пока, как временное решение, можно вставить DI перед LD SP,IY и EI после POP HL и поглядеть, будет все тормозить или нет =)
Со старым обработчиком, разумеется. То, что выше набросал, должно вообще конкретно портить карту.

reddie
17.08.2021, 15:23
Что ж, решение все-таки через индексную пару. Добавляет 40 тактов к каждой линии, но это лучший вариант из имеющихся.
Ограничения: пара AF портится, если идет переброс; пара BC задействована под "резервные" байты при смене стека.
В начале вывода каждой строки (линии) добавляется две команды:



ADD IY,DE
LD B,(IY-1) ;в паре BC будет 2 байта
LD C,(IY-2) ;под "новым" стеком
LD SP,IY


Сам обработчик тоже переделан из-за дополнительных проверок:



INTERR LD (CURRSP+1),SP
LD SP,INTBUF+100
PUSH AF,BC,DE,HL... ;сохраняем все изменяемые регистры
BLABLA ;процедуры на прерываниях
......
POP ...HL,DE,BC ;восстанавливаем, кроме AF
LD A,(CURRSP+2) ;старший байт стека
RLA ;если SP>=#8000 - идет переброс
JR C,CURRSP
POP AF ;иначе нормальный выход
LD SP,(CURRSP+1)
EI
RET

CURRSP LD SP,0 ;пару AF в коде вывода не используем!
POP HL ;адрес возврата в процедуру вывода
LD (RET_JP+1),HL
DEC HL
LD A,(HL) ;если последней выполненной
CP #F9 ;командой было LD SP,IY
JR NZ,RET_JP-6 ;то восстанавливаем из BC
DEC HL ;проверяем оба байта
LD A,(HL) ;для надежности
CP #FD ;#FDF9 для IY или #DDF9 для IX
JR NZ,$+6
PUSH BC ;восстанавливаем из BC
POP BC
JR $+7
LD HL,(INTBUF+92)
PUSH HL ;иначе востанавливаем из HL
POP HL
EI
RET_JP JP 0

INTBUF DEFS 100 ;буфер стека



Логика работы: обработчик смотрит, где стек, если переброса нет - нормальный выход.
Если переброс и последняя выполненная команда была LD SP,IY - в стек бросается заранее взятая ниже пара BC.
Иначе бросается HL, что даст верное восстановление хоть до, хоть после смены стека.

Можно схитрить: если карта начинает выводиться сразу после обработки прерывания (после HALT),
то в код вывода "первого куска" загрузку BC из IY можно не вставлять - кусок точно успеет вывестись до прерывания.
Каждая загрузка 40 тактов (с wait), х40 = 1600т на куске. х4 = 6400, поэтому смысл экономить есть.

izzx
17.08.2021, 19:05
Что ж, решение все-таки через индексную пару. Добавляет 40 тактов к каждой линии, но это лучший вариант из имеющихся.

Спасибо! Прикрутил новый обработчик прерываний и процедуру вывода дополнил. Пока что одинаково все четыре части. По моему, карта не портится. Во всяком случае, два раза пролетел, не заметил артефактов. Может и раньше, конечно, не каждый раз бы попадало неудачно.
Только теперь начало сваливаться в 16 кадров, когда спрайтов много, но я думаю, первые два куска карты сделать без лишних строк вывода и всё влезет. До этого только когда происходил подрыв ГГ, не успевало в 2 фрейма. Должно и сейчас уместиться.

Осталось найти где глюк после перемещения кода на новые адреса ). Если не стрелять, то всю карту можно пролететь. А иначе скорый зависон.

reddie
17.08.2021, 19:17
По моему, карта не портится. Во всяком случае, два раза пролетел, не заметил артефактов
Если не затруднит, можно проверить: остановить скролл карты (увеличение переменной смещения от начала карты),
будет выводиться один и тот же экран все время. Артефакты на стоячей картинке ловить куда проще =)
До кучи можно и вывод врагов отрубить, чтоб не мешали. Ну или если они привязаны к смещению - их и не будет.


думаю, первые два куска карты сделать без лишних строк вывода и всё влезет
В турборежиме - да. Понимаю, что игра на него и заточена, но все же. Без турбы влезет только 1/4
Хотя можно по приколу вырубить турбо и наблюдать за порчей карты =) особенно если остановить скролл.

izzx
17.08.2021, 20:45
можно проверить: остановить скролл карты
Не так то просто дождаться порчи карты. Остановил её, погонял на эмуляторе. Только один раз добился артефактов, да и то наверное потому что понажимал огонь.
Пробовал и с двойной защитой вывод и с одинарной.
Редкое явление наверное. Вывод фона идёт почти сразу после halt, так что прерывание попадает в одно и то же место, я думаю. На реале бы погонять ещё надо.

Evgeny Muchkin
17.08.2021, 22:21
Пока с прогонами на реале не могу помочь, ибо в загородном. Но как только буду в городе, подсоблю всенепременнейше!

reddie
17.08.2021, 22:49
Вывод фона идёт почти сразу после halt, так что прерывание попадает в одно и то же место
По идее, на прерываниях висит вывод музыки, а он никак не может жрать одинаково.
То есть сперва отыграл музон, потом пошло все остальное. Обычно делается так, за исключением мультиколорных дем.
И что-то сомневаюсь насчет 3 тыс. тактов на музыку... хотя вполне может быть, смотря кто и как писал исходный код.
Например, с оптимизацией родного плеера Sound Tracker вообще не парились, там плавало от 3 до 10 тыс примерно.

izzx
18.08.2021, 08:36
Пока с прогонами на реале не могу помочь, ибо в загородном. Но как только буду в городе, подсоблю всенепременнейше!
Хорошо. Можно, кстати, движок для GS не спеша высылать.
Думаю сделать звуки:
1. Выстрел
2. Попадание в элементы фона
3. Попадание во врага
4. Скольжение по краю
5. Взрыв ГГ
6. Взрыв врага
Это для себя план пишу.


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

И что-то сомневаюсь насчет 3 тыс. тактов на музыку... хотя вполне может быть, смотря кто и как писал исходный код.
Я там в плеере переделывал на порты ZX, может ещё замедлил )

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

reddie
18.08.2021, 10:28
Двойная защита карты только для третьей части из четырёх. На турбо на неё попадает прерывание, а на не турбо как повезёт )
А это можно тоже на бордюр выводить, кстати =) Прямо в выходе из обработчика дописать пару команд, там только регистр A нужен, который все равно портим.
То есть там, где идет push-pop из BC, до них или после них поставить зеленый/белый/какой еще, а на входе в обработчик после сохранения регистров - черный.
Если "попались" на SP,IY- бордюр моргнет. Только тогда нужно убрать все остальные выводы в порт.

izzx
18.08.2021, 11:14
А это можно тоже на бордюр выводить, кстати =)
А я просто точку останова поставил в эмуле. Изредка, но попадает на push bc: pop bc в обработчике. Значит, не зря старались.

Какие идеи по отлову бага? Может я не знаю чего. После зависания захожу в отладчик, а там регистр PC уже далеко уехал, история на экране затёрлась. Как найти точку сбоя? Методом перебора или исключения пока не подловил.

Да, ещё мысль: получается, можно целиком экран за два прерывания выводить. Но ни на что другое времени не останется. Думаю, может видео ролик можно склепать на 7 секунд со скоростью 16 кадров из 120 страниц памяти, без всякого сжатия. Это в порядке баловства.
upd. Или 25 кадров.

reddie
18.08.2021, 12:34
получается, можно целиком экран за два прерывания выводить
В турборежиме - да. Еще и время остается на что-нибудь. Маленькое.
Но в игре лучше ограничиться тем окном, что уже есть =)

По поводу бага... хз, даже не смотрел еще версию с последними изменениями.
Можно попробовать тот же метод с бордюром: зарезервировать для каждой части кода свой цвет.
То есть 8 цветов в наличии, черный - вывод карты, синий - вывод спрайтов, красный - музыка,
зеленый - блок логики и так далее. Таким образом визуально отсечь проблему.
Дальше уже дробить на более мелкие "кусочки" исследуемый код.
В обработчике прерываний, естественно, бордер не трогаем, да и красным не мигаем (когда 16 фпс).
Кстати, а как именно оно виснет? Музыка продолжает играть или тоже вешается с воем?

izzx
18.08.2021, 13:51
Кстати, а как именно оно виснет? Музыка продолжает играть или тоже вешается с воем?
Когда как. Чаще продолжает играть.
Наставил цветов бордера в разных местах - на разных цветах виснет ). Убрал вывод карты совсем - тоже виснет.
Интересно. Буду думать. Где-то страница, наверно, не та включатся.

reddie
18.08.2021, 14:10
Где-то страница, наверно, не та включатся.
Если глюки начались после работ по переделке вывода - скорее всего, что да, нужно копать в перемещенных процедурах.
Либо не та страница врубается, либо код не полностью "перемещен", либо что-то убивается на прерываниях.
Но последний вариант маловероятен, для этого нужно не сохранять какую-то регистровую пару.
Скорее всего, перемещенный код вызывает что-то по прежним адресам, а там уже пусто. Точнее, вывод экрана.

izzx
18.08.2021, 19:20
Порядок. Виновата процедура печати счётчика кадров. Если счётчик не включать, то не виснет. Но почему так - не знаю ). Потом разберусь.
Чуть погонял не в турбо режиме - в одном месте карта попортилась. Но это ничего, так задумано.

izzx
25.08.2021, 20:04
Отловил глюк со счётчиком. Выложил текущую версию. На железе выдаёт 25 кадров, иногда сваливается в 16, когда на границе экрана 2 и более спрайта. Потому что вывод целых спрайтов идёт быстрым способом, а на границе экрана, когда видна часть спрайта, медленным.
Думаю, есть ещё резервы для оптимизации.
Используется примерно 89 страниц памяти из 128.

reddie
25.08.2021, 21:20
а на границе экрана, когда видна часть спрайта, медленным способом
Вот тут стоит покопаться =)) По идее, наоборот, кусок спрайта должен выводиться быстрее, чем целый.
Бегло порыл список файлов в исходниках, вроде как вывод частичного спрайта - файл EG_Sprites_Partial.asm только это уже Спектрум или еще Амстрад? =)
Толком не разбирался, но параметры такие: высота спрайта, кажись, 10 "пикселей", если байт нулевой - пропускаем. Иначе затираем байт атрибутов целиком, а не половинки по маске (как я думал).
Далее, после каждого впечатанного байта идет конструкция ex de,hl: and a: sbc hl,bc: ex de,hl - при таком пересчете, естественно, вывод будет жрать кучу времени.
Если правильно понял - спрайты выводятся вертикально, по столбцам. Хуже может быть только вывод крест-накрест =)) Желательно переделать на нормальный, построчный, вместе с форматом спрайтов.
После каждой линии пара DE загружается из адресуемой через IX таблицы - тоже прожорливо. Точнее, это получаются не линии, а столбцы, раз выводим вертикально, а 10 - это ширина.


PrnSprLoopL5
push bc
ld bc,80

ld e,(ix+0)
inc ixl
ld d,(ix+0)
inc ixl


Но если выводим вертикально - зачем брать следующий адрес из таблицы? Достаточно сохранить-восстановить DE из стека и увеличить на 1.
Сам вывод можно развернуть в коде, благо, свободная память имеется. Конструкциями вида LD (HL),NN:INC L, либо сразу INC L, если байт пустой.
Потребуется свой кусок кода для каждой "частичной" фазы, как и для целого спрайта, но вывод будет пошустрее.
И намного пошустрее, если выводить линиями (горизонтально), ибо для перехода на следующую будет достаточно сделать ADD HL,BC (ВС=80-ширина спрайта, что-то типа того)

izzx
25.08.2021, 22:41
вывод частичного спрайта - файл EG_Sprites_Partial.asm
Да. Там для каждого случая свой раздел. Вывод слева по 1,2,3,4,5 столбиков, то же для правой части. И сверху-снизу тоже часть умеет выводить. Адреса хранятся для каждой пары строк. Высота 21 строка - значит 11 адресов на спрайт.
Вот так я писал:
Спрайт занимает 126 байт, 6 байт ширина, 21 высота, занимаемое место округляется до 128
Выводится в таком порядке байт:
01 03 05 07 09 11
00 02 04 06 08 10
13 15 17
12 14 16 18...
А последняя строчка по порядку 120 121 122 123 124 125.
Причём нули пропускаются, не выводятся. Поэтому не надо маски.

Вот переделывать формат спрайтов мне меньше всего охота было )
При быстром выводе через ld (NN),hl хитрый формат спрайта почти не влияет на скорость. Там по строчкам выводится всё равно.
А тут да, очень криво выходит.

reddie
26.08.2021, 08:37
Причём нули пропускаются, не выводятся. Поэтому не надо маски.
Но тогда получается, что затираем сразу два "пикселя" экрана (карты), даже если один из них пустой в спрайте. Или там у спрайтов все "пиксели" по два шириной?

Вот переделывать формат спрайтов мне меньше всего охота было )
А надо бы =)) Ибо вертикальный вывод плюс еще пересчет адресов строк сжирают кучу времени.

При быстром выводе через ld (NN),hl хитрый формат спрайта почти не влияет на скорость
Не, это другие команды. LD (NN),HL подходит для переброса карты через стек по 2 байта сразу, а вот спрайты без стека и побайтово, через LD (HL),N - стек не трогается.
То бишь каждый спрайт развертывается в кусок кода. Например, спрайт (условно) 4х4 пикселя, пустой байт - ноль, заполненный (1) - FF. Сам спрайт такой:

0110
1111
1001
0110

Код вывода для него развертывается вот в такой кусок:




INC L ;0
LD (HL),#FF ;1
INC L
LD (HL),#FF ;1
INC L ;0, INC для корректного пересчета HL
ADD HL,BC

LD (HL),#FF ;1
INC L
LD (HL),#FF ;1
INC L
LD (HL),#FF ;1
INC L
LD (HL),#FF ;1
ADD HL,BC

LD (HL),#FF ;1
INC L
INC L ;0
INC L ;0
LD (HL),#FF ;1
ADD HL,BC

INC L ;0
LD (HL),#FF ;1
INC L
LD (HL),#FF ;1
RET ; последний INC уже не нужен,
; если код развертки это обработает


Для перехода вниз на строку достаточно одной команды ADD HL,BC. Усеченные спрайты (по ширине) будут выводиться еще быстрее.
В данном конкретном примере #FF можно было бы заменить на регистр, но в реальности там будут разные значения, потому - числами.
Согласен, переделывать формат и вывод - занятие не на полчаса, но если уж делать, то делать =))

izzx
26.08.2021, 14:00
Но тогда получается, что затираем сразу два "пикселя" экрана (карты), даже если один из них пустой в спрайте. Или там у спрайтов все "пиксели" по два шириной?
Да, сразу по два пикселя. Может спрайты нарисованы с учётом этого, не заметно.


Не, это другие команды. LD (NN),HL подходит для переброса карты через стек по 2 байта сразу, а вот спрайты без стека и побайтово, через LD (HL),N - стек не трогается.
Да, перепутал команды. Для спрайтов LD (HL),N.


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

зы. Попробую переформатировать спрайты, чтобы выводить по линиям. За счёт переходов хотя бы ускоримся.

izzx
03.09.2021, 21:57
Теперь должны быть стабильные 25 кадров.
Переделал формат спрайтов для вывода по частям.
Оптимизировал пару процедур и сделал так, чтобы в тот момент, когда проседал FPS, второстепенный код не выполнялся.
Выложил тест. Если не укладывается в два прерывания, мигает (мигал бы) красным.

reddie
04.09.2021, 10:31
сделал так, чтобы в тот момент, когда проседал FPS, второстепенный код не выполнялся

Если он "второстепенный" и не критичен для работы - может, его вообще убрать? =))
Поглядел изменения - не во все въехал. Трудновато вникать в чужие исходники, да еще переписанные по диагонали (эт я про адаптацию с Амстрада).
Но озадачил вот этот момент (файл EG_Sprites10.asm):



; sprites are 6 bytes wide, so repeat another 5 times
ld a,(hl)
ld (de),a
inc e;
; res 3,h
;---
push de
ld de,80
and a
sbc hl,de
pop de
;---
ldi
; set 3,h
;---
push de
ld de,80
add hl,de
pop de
;---
;
ld a,(hl)
ld (de),a
inc e
; res 3,h
;---
push de
ld de,80
and a
sbc hl,bc
pop de
;---
ldi
; set 3,h
;---


Если это вывод спрайтов - он так и остался вертикально-побайтово. Но даже если это что-то иное - процедура значительно похудеет и ускорится, если убрать LDI оттуда.
Заменяем LDI на 4 команды (LD A,(HL):LD (DE),A:INC E/DE:INC L/HL), зато освобождаем пару BC, и можно убрать PUSH-POP DE, заменив в сложении/вычитании DE на BC:



and a
sbc hl,bc


Еще и не нужно будет каждый раз загружать число 80 в BC.

- - - Добавлено - - -

Подумал - LDI можно оставить, просто после каждого LDI добавить команду INC C (чтобы восстановить значение 80) - это быстрее и короче, чем 4 команды вместо LDI.
Достаточно загрузить один раз 80 в начале процедуры и все. Основное же изменение останется (уборка PUSH-POP DE и замена DE на BC).

izzx
04.09.2021, 10:54
Если он "второстепенный" и не критичен для работы - может, его вообще убрать? =))
Второстепенными я посчитал обновление счётчика рекорда и жизней для второго экрана. У меня просто копируются эти области командами ldi на второй экран. Иначе будет разница. Печатается то на одном экране.
И плюс ещё звёзды перестают мерцать во время взрыва игрока. Это самый загруженный момент. FPS проседало тут.

Если это вывод спрайтов - он так и остался вертикально-побайтово. Но даже если это что-то иное - процедура значительно похудеет и ускорится, если убрать LDI оттуда.

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

reddie
04.09.2021, 16:16
Еще один файлик для возможной оптимизации - EG_Sprites_Player вроде это вывод спрайтов игрока.



inc ixl
inc ixl

ld l,(ix+0) ;2я строка
ld h,(ix+1)
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0
and a
sbc hl,bc ;1я строка
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0
inc hl
;ld (hl),0


Все инструкции LD (HL),0 закомментированы, и там куча пустых строк с ними.
Проверить бы всю строку, если она пустая - зачем щелкать HL, можно сразу переходить на следующую.
И еще, какие-то извращенские методы доставания адреса строк через IX, это жрет много времени =)
Получается, что на каждую вертикальную координату экрана своя таблица адресов?
Сколько ж эти таблицы жрут? Или я не догоняю логику работы процедуры вывода?
Саму процедуру можно ускорить, для начала заменив IX и работу с ним на DE:




EX DE,HL
LD E,(HL)
INC L
LD D,(HL)
INC HL
EX DE,HL


Выигрыш где-то 20 тактов на переход. Немного, но... либо задействовать DE во втором ускорении ниже.
Второе: в коде куча повторяющихся байт, наиболее частый, вроде, 45. Можно распихать самые частые по регистрам.
Свободных регистров у нас получается три: A, E и D (если оставить IX), либо только A.
То бишь пишем в память не числа, а регистры. Получится строка типа такой (заменив 45 на аккум):



ld l,(ix+0) ;2я строка
ld h,(ix+1)
;ld (hl),0
inc hl
ld (hl),a
inc hl
ld (hl),41
inc hl
ld (hl),a
inc hl
ld (hl),a
inc hl
ld (hl),8
and a
sbc hl,bc ;1я строка
ld (hl),9
inc hl
ld (hl),a
inc hl
ld (hl),61
inc hl
ld (hl),63
inc hl
ld (hl),a
inc hl
ld (hl),a

inc ixl
inc ixl


Выигрыш тоже небольшой, однако по чуток, да набирается экономия, если еще и адреса из DE брать.

izzx
04.09.2021, 18:13
Проверить бы всю строку, если она пустая - зачем щелкать HL, можно сразу переходить на следующую.
Вот это правильная мысль, конечно. У меня в генераторе на C# код стругается, теоретически можно бы сделать что угодно.


Получается, что на каждую вертикальную координату экрана своя таблица адресов?
SpriteAddrHigh ds 170 ;адреса спрайтов по 2 байта для каждых 10 пар строчек и одной последней = 22 байт на спрайт *7 штук и 16 на лазер
То есть рассчитывается адрес начала каждой второй строчки. 1,3,5,..21.
Потом при печати берём адрес второй строчки, рисуем её, поднимаемся на строку выше, рисуем её. Так перешло от версии амстрад.
Я выше говорил, что это нужно для частичной печати спрайта на верхней и нижней границе экрана.


Саму процедуру можно ускорить, для начала заменив IX и работу с ним на DE:
Наверное тоже можно бы, но тогда надо хранить-восстанавливать DE. А так адрес таблицы для спрайта всегда в IX, никто его не трогает. Все спрайты один за другим печатаются по таблице из IX.



То бишь пишем в память не числа, а регистры. Получится строка типа такой (заменив 45 на аккум):
Идею понял, но это хитро надо генератор на C продумывать ). Чтобы он такой умный код выдал.

ps. Да, если смотреть исходники с подсветкой синтаксиса, то закомментированное не так бросается в глаза. Я вот в Notepad++ редактирую.

ps2. Вот тут в прошлый раз так оптимизировал расчёт координат спрайтов, в случае когда регистры, кроме IY и IX, заняты и запись через стек. Нужно сдвинуть DE на 80 вниз.
Было:


;---
ld (TempHL),hl ;16 на строку вниз
ld hl,80 ;10
add hl,de ;11
ex de,hl ;4
ld hl,(TempHL) ;16
; ;---
push de ;11 ; place address into address list pointed to by sp
;68

Стало:


ld iy,80 ;14
add iy,de ;15
push iy ;15
;44

izzx
09.09.2021, 20:36
Очередная оптимизация.
Теперь формат спрайтов полностью линейный и последовательный.
Формат шрифтов и значков жизней остался Amstrad.
Финальная заставка и затемнение картинки загрузчика сделаны через два экрана.

Закомментированы лишние команды inc hl при выводе спрайтов, когда строчка пустая.

SpritesPrint0
ld bc,80-5
;ld l,(ix+0) ;1я строка
;ld h,(ix+1)
;ld (hl),0
;inc hl
;ld (hl),0
;inc hl


Спрайты в прямом формате
0 1 2 3 4 5 6
7 8 9 ...
............125

Вместо конструкции типа

and a
sbc hl,bc
везде стало

add hl,bc
Сэкономили целых 8 тактов.
Кроме того, без лишних переходов процедура подсчёта адресов спрайтов сразу похудела тысячи на 4 тактов.

Ещё не все советы от reddie реализованы, можно оптимизировать долго ).