«Я тут пишу код и мне хотелось бы для этого иметь EX HL,HL', было бы удобнее, быстрее и компактнее». Это подразумевает, что мы берём быстрые циклы для Z80 и ищем как их ускорить. Но качественные реформы, вроде расширенного блока регистров предложенного выше, на самом деле радикально меняют весь подход к оптимизации кода.
радикально изменить подход может и одна-единственная команда
например, ex sp,hl вместо (или вместе с) ld sp,hl
То есть я о чём. Просто набор команд недостаточен, чтобы сказать, это хорошо и плохо. Нужно знать растактовки команд и смотреть как будут выглядеть решения стандартных задач. И только там станет понятно, где у нас улучшено, а где просто изменено.

Я крайне с этим согласен!
И скорее всего сейчас начну говорить жуткую банальщину, но как раз эта вот растактовка меня давно уже не удовлетворяет в том же Z80.
Взять ту же самую LDIR. Блочная инструкция, двухбайтовая, пересылающая сразу блоки байт, но как?
А так что на каждый пересылаемый полезный по «ПН (полезной нагрузке)» байт сопровождается двумя считываемыми байтами инструкции и скорее всего еще больше тактов по общему смыслу.
Не сомневаюсь что это всё поднималось ранее тысячи раз.
Потому что ну реально концепция general processor на каждый байт обрабатываемых полезно данных нагрузки реально чаще всего даёт +10 байт инструкций, опкодов и адресов. Который нужно считывать.
В итоге LDIR работает полукалечно: просто выполняет LDI и делает JR -2, чтобы снова считывать 2 байта инструкции чтобы переслать 1 байт полезных данных.
И это дико бесит перфекциониста типа меня и рождает DMA-контроллеры. Просто потому что general processors сосут. Откровенно.
Понятно что там еще с прерываниями надо разобраться и тому подобное. Не всегда уместно блочить шины DMA-контроллером надолго. Может и звук защёлкать и так далее.
Но я хотел сказать лишь то что наверняка уже тысячи раз говорилось: хотелось бы чтобы в general processor были по настоящему эффективные инструкции могущие не пересчитывать байты инструкции x2-3 байта из памяти каждый раз пересылая лишь 1 байт полезных байт нагрузки.
А LDIR настолько такой не является, что её реально попают пушами.
Аааа…
Мне кажется, что думать в терминах конкретных инструкций бесперспективно. Потому что как выглядят большинство хотелок в обсуждениях этого типа? «Я тут пишу код и мне хотелось бы для этого иметь EX HL,HL', было бы удобнее, быстрее и компактнее». Это подразумевает, что мы берём быстрые циклы для Z80 и ищем как их ускорить. Но качественные реформы, вроде расширенного блока регистров предложенного выше, на самом деле радикально меняют весь подход к оптимизации кода. Например, мы привыкли что самая быстрая заливка на Z80 — серия команд PUSH. А на Sharp LR35902 команда PUSH занимает уже не 11, а 16 тактов, и это при том что добавлена новая 8-тактовая команда LD (HL+),A, которая по сути может залить байтами с той же скоростью что и PUSH.

То есть я о чём. Просто набор команд недостаточен, чтобы сказать, это хорошо и плохо. Нужно знать растактовки команд и смотреть как будут выглядеть решения стандартных задач. И только там станет понятно, где у нас улучшено, а где просто изменено.
в последнее время подумываю, как расширить зетник для работы с регистровым файлом по типу 6502
В Sharp LR35902 в последние 256 байт памяти замапили четыре иструкции LDH (imm8), a / LDH a, (imm8) / LDH (c), a / LDH a, (c), но блин увы, там обратная совместимость серьёзно была подпорчена и потому эти инструкции заняли место других из i8080 и будучи однобайтовыми опкодом действительно были судя по всему штукой интересной. А чтобы не ломать совместимость с Z80 тут надо тоже вклиниваться в ED-коды и 2 байта на опкод уже сильно ущемляют полезность потому что ничем не лучше однобайтного LD A,(imm6).
В связи с этим вопрос — какие то пути преодоления проблемы просматриваются? :)
  • avatar bfox
  • 2
посмотри в сторону 6309
  • avatar tsl
  • 1
В контроллерах, для которых разрабатывают свои ISA, специализированные инструкции для железа вполне норма. Раз некст «улучшенный», то почему не улучшить и проц? Говорить о том, что в з80 не хватает элементарных вещей, наверное излише. Но я не столько сами их инструкции не понимаю, сколько scope (забываю русский) ихнего дизайна. Что они хотят выносить на аксели, а что на проц? Выглядит как-то сумбурно.
Ну и щас скажу ужасное (instrospec, закрой глаза): в последнее время подумываю, как расширить зетник для работы с регистровым файлом по типу 6502.
«ну запилите вы прозрачную трансляцию адреса»

Чем больше размышляю над этой идеей тем больше нравится она мне. «Выпрямить» адресацию реально же можно просто анализом и перестановкой сигналов на шине адреса. Но с другой стороны у PIXELAD еще выполняются сдвиги, т.е. вычисляется уже готовый адрес по осмысленным готовым координатам в DE.
Я конечно согласен, что выглядит это FPGA-роскошество как изврат над самой идеей general processor, а сам слой ULA при наличии нормально скроллируемых тайлового слоя и 8bpp вообще непонятно зачем нужен кроме совместимости, но я всё-таки даже восхищаюсь этим непотребством, уже больно оно так сказать по мозолям топчется. :)
Да, дизайн уровней просчитан заранее. Процесс их создания наполовину автоматизирован, поэтому, когда собираешь уровень, то можно просчитать будущую нагрузку на CPU.

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

А такого, чтобы спрайт игрока перекрашивал собой фоновые блоки — я не видел.
(Хотя есть одно исключение: взять «звезду» и подойти вплотную к белому блоку, у которого цвет идентичен цвету фона)
а сейчас разве не просчитан дизайн заранее?

и кстати, с чисткой атрибутов не всё в порядке, иногда спрайт игрока перекрашивает фоновые платформы
Графика рисуется через стек, и оставлять прерывания небезопасно.
Я, конечно, закладываю некое свободное время во фрейм. Но оно тоже может не успеть, на какой-нибудь супер-медленной машине.

Рендеринг всей графики в игре должен занимать не более ~80% фрейма. Это как бы теоретический максимум.
Но вполне может быть, что если сцена окажется перенасыщенной, и тогда и вывод графики вылезет за фрейм.
Этого нужно избегать еще на этапе дизайна уровня — использовать автоматические метрики, которые подскажут опасность «выхода за фрейм».
зачем прерывания запрещать на графике, если не успеть может только логика?
В памяти в RAM 2 по статичным адресам находятся четыре стека (по два стека на два экрана):
— 204 байта отведено под сохранение 2х-байтных адресов из области атрибутов, которые были «покрашены» в текущем фрейме, и которые нужно почистить позже;
— +2 байта на указатель стека;
— 48 байт отведено под хранение 16 «троек», где первые два байта — это адрес в экранной области, куда был выведен спрайт в текущем фрейме, и еще 1 байт кодирует размер выведенного спрайта;
— +2 байта на указатель стека.

Когда приходит время почистить экран и атрибуты:
— выгребаем по-порядку адреса из атрибутного стека, и вливаем по ним цвет фона;
— затем выгребаем «тройки» из второго стека и выводим на экран пустые прямоугольники: адрес в экране знаем, размер спрайта знаем.

Такие размеры стеков были выбраны исходя из того, что на экране не может быть более 16 динамических объектов одновременно (и то — это с большим запасом; в 3.5 Мгц столько не влезет). Средний размер одного спрайта 3x2 = 6 знакомест. Бывают также спрайты размерами по 2 (оружие), 4 (враг, приз) или 8 знакомест.

Помнить, что было под спрайтом, не нужно. Каждый раз всё заново перерисовывается.
И сразу вопрос — «с занесением в стек информации для очистки в следующем фрейме» — что именно тут подразумевается? Т.е. вопрос вот в чём, что лежит под спрайтом вроде легче выяснить при выводе статических объектов. Но у тебя это разнесено. Т.е. ты либо должен восстанавливать, что у тебя под спрайтом ещё раз, либо сохранять экранные данные, либо?
Надеюсь, это не прозвучит обидно, но мне прочесть этот текст было интереснее, чем поиграть в саму игру!
Движок — это конвеер, который в каждом кадре делает одну и ту же работу.

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

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

Далее опрашивается user input (клавиатура или джойстик). На основе этого для главного героя выбирается спрайт, и придаётся ускорение по осям (разбег, прыжок, торможение, итд).

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

После прогонки всех объектов по конвееру, последним в очереди идёт проверка общеигровых событий, таких как: достижение конца уровня, переключение режимов ГГ (immunity, starman, итп), проверка возможности захода в «трубы», и т.п.

Затем просто ждём следующего прерывания, меняем экраны местами, и поехали с начала.

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

В целом, у движка есть три режима работы:
— обычный геймплей, когда работают и рендер и логика;
— анимация («взятие приза», «заход в трубу», «скатывание по флагу») — работает только рендер, а логика управляется специально написанным скриптом;
— распаковка данных в память, тут 100% CPU отдаётся на распаковку, а на экране в это время показываются попеременно два статичных кадра, и просто играет музыка.

Если интересно, могу более подробно расписать по любому пункту.
Очень интересно было бы почитать (я где то слышал что на профильных форумах обсуждение было) как движок работает.
Я в эмуляторе немного анализировал что в экранной области происходит — как понял основная идея, что пустые области попиксельно никак не обрабатываются, а скроллится только активное содержимое. Но всё равно интересно какие структуры и т.п.
Ибо действительно fps и отзывчивость фантастические для спектрума.
Нет, не стоит. Потому что это всё, во-1, необязательно признак неумелости, а во-2, не настолько большинство, как линейный буфер. Контрпримеры легче найти гораздо, чем примеры с буфером в юлашной раскладке.
Подавляющее большинство игрушек не умеют делать vsync. Подавляющее большинство игрушек обновляют экран раз в 3 фрейма или даже медленнее (offscreen buffer — одна из важных причин этого). Подавляющее большинство игрушек shipped с артефактами изображения. Мне продолжать?
В подавляющем, абсолютном большинстве игрушек линейный буфер, так что и умеющие так делали. Для игродела плюсов в спековской раскладке весьма немного и они только в очень частных случаях проявляются. А вот неудобства намного чаще.
Да, я согласен что очень многие игры на спектруме написаны людьми, на спектруме программировать не умеющими!