Мне кажется, что думать в терминах конкретных инструкций бесперспективно. Потому что как выглядят большинство хотелок в обсуждениях этого типа? «Я тут пишу код и мне хотелось бы для этого иметь 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).
В связи с этим вопрос — какие то пути преодоления проблемы просматриваются? :)
В контроллерах, для которых разрабатывают свои 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 с артефактами изображения. Мне продолжать?
В подавляющем, абсолютном большинстве игрушек линейный буфер, так что и умеющие так делали. Для игродела плюсов в спековской раскладке весьма немного и они только в очень частных случаях проявляются. А вот неудобства намного чаще.
Это очень хороший анекдот. Непонимание раскладки ULA — натурально, бич начинающих кодеров. Она действительно не всегда удобна, но думаю многие со мной согласятся, что с опытом приходит понимание, что она намного эффективнее линейной раскладки.
То есть я о чём. Просто набор команд недостаточен, чтобы сказать, это хорошо и плохо. Нужно знать растактовки команд и смотреть как будут выглядеть решения стандартных задач. И только там станет понятно, где у нас улучшено, а где просто изменено.
В связи с этим вопрос — какие то пути преодоления проблемы просматриваются? :)
Ну и щас скажу ужасное (instrospec, закрой глаза): в последнее время подумываю, как расширить зетник для работы с регистровым файлом по типу 6502.
Чем больше размышляю над этой идеей тем больше нравится она мне. «Выпрямить» адресацию реально же можно просто анализом и перестановкой сигналов на шине адреса. Но с другой стороны у PIXELAD еще выполняются сдвиги, т.е. вычисляется уже готовый адрес по осмысленным готовым координатам в DE.
Я конечно согласен, что выглядит это FPGA-роскошество как изврат над самой идеей general processor, а сам слой ULA при наличии нормально скроллируемых тайлового слоя и 8bpp вообще непонятно зачем нужен кроме совместимости, но я всё-таки даже восхищаюсь этим непотребством, уже больно оно так сказать по мозолям топчется. :)
По поводу атрибутов: — есть три бага, которые ломают мне мозг уже не один месяц, и которые (пока) не удалось победить без удорожания стоимости отрисовки. Два из них воспроизводятся при вышибании первых и последних блоков в движении. И еще один: — не очищается самая левая колонка атрибутов, когда блок уезжает за левую границу.
А такого, чтобы спрайт игрока перекрашивал собой фоновые блоки — я не видел.
(Хотя есть одно исключение: взять «звезду» и подойти вплотную к белому блоку, у которого цвет идентичен цвету фона)
и кстати, с чисткой атрибутов не всё в порядке, иногда спрайт игрока перекрашивает фоновые платформы
Я, конечно, закладываю некое свободное время во фрейм. Но оно тоже может не успеть, на какой-нибудь супер-медленной машине.
Рендеринг всей графики в игре должен занимать не более ~80% фрейма. Это как бы теоретический максимум.
Но вполне может быть, что если сцена окажется перенасыщенной, и тогда и вывод графики вылезет за фрейм.
Этого нужно избегать еще на этапе дизайна уровня — использовать автоматические метрики, которые подскажут опасность «выхода за фрейм».
— 204 байта отведено под сохранение 2х-байтных адресов из области атрибутов, которые были «покрашены» в текущем фрейме, и которые нужно почистить позже;
— +2 байта на указатель стека;
— 48 байт отведено под хранение 16 «троек», где первые два байта — это адрес в экранной области, куда был выведен спрайт в текущем фрейме, и еще 1 байт кодирует размер выведенного спрайта;
— +2 байта на указатель стека.
Когда приходит время почистить экран и атрибуты:
— выгребаем по-порядку адреса из атрибутного стека, и вливаем по ним цвет фона;
— затем выгребаем «тройки» из второго стека и выводим на экран пустые прямоугольники: адрес в экране знаем, размер спрайта знаем.
Такие размеры стеков были выбраны исходя из того, что на экране не может быть более 16 динамических объектов одновременно (и то — это с большим запасом; в 3.5 Мгц столько не влезет). Средний размер одного спрайта 3x2 = 6 знакомест. Бывают также спрайты размерами по 2 (оружие), 4 (враг, приз) или 8 знакомест.
Помнить, что было под спрайтом, не нужно. Каждый раз всё заново перерисовывается.
Сначала идёт обработчик прерывания, на нём висит музыка, звуковые эффекты, и глобальные счётчики фреймов.
Затем идёт блок рендера. На время вызова графических процедур, прерывания запрещаются.
Рендер состоит из следующих шагов:
— очищаются все динамические объекты на экране (герой, враги, призы, файлболлы, — всё, что движется);
— выводятся неподвижные блоки, причём очищать их не нужно, т.к. «с хвоста» блоки чистят сами себя;
— поверх блоков выводятся все динамические объекты, с занесением в стек информации для очистки в следующем фрейме;
Далее опрашивается user input (клавиатура или джойстик). На основе этого для главного героя выбирается спрайт, и придаётся ускорение по осям (разбег, прыжок, торможение, итд).
Затем запускается конвеер обработки всей логики. Она полностью одинакова для всех движущихся объектов в сцене. Каждый объект прогоняется по этому конвееру: — изменяется состояние, координаты, детектируются коллизии, срабатывают события и происходит обработка реакции на события (если они заданы).
Весь алгоритм линейный, т.е. обработка всех физических свойств, всех коллизий и событий для одного объекта всегда занимает примерно одинаковое время, независимо от игровой ситуации.
После прогонки всех объектов по конвееру, последним в очереди идёт проверка общеигровых событий, таких как: достижение конца уровня, переключение режимов ГГ (immunity, starman, итп), проверка возможности захода в «трубы», и т.п.
Затем просто ждём следующего прерывания, меняем экраны местами, и поехали с начала.
Если не успели обработать всю логику, и прерывание пришло раньше, — ничего страшного. Пытаемся компенсировать это время в следующем кадре (т.е. не ждём прерывания, а сразу начинаем готовить следующий кадр).
В целом, у движка есть три режима работы:
— обычный геймплей, когда работают и рендер и логика;
— анимация («взятие приза», «заход в трубу», «скатывание по флагу») — работает только рендер, а логика управляется специально написанным скриптом;
— распаковка данных в память, тут 100% CPU отдаётся на распаковку, а на экране в это время показываются попеременно два статичных кадра, и просто играет музыка.
Если интересно, могу более подробно расписать по любому пункту.
Я в эмуляторе немного анализировал что в экранной области происходит — как понял основная идея, что пустые области попиксельно никак не обрабатываются, а скроллится только активное содержимое. Но всё равно интересно какие структуры и т.п.
Ибо действительно fps и отзывчивость фантастические для спектрума.