Несколько лет назад я написал игровой движок на основе плитки для Apple IIgs на языке ассемблера 65816. Это была довольно медленная машина, и программирование «на металле» является виртуальным требованием для получения приемлемой производительности.
Чтобы быстро обновить графический экран, необходимо отобразить стек на экран, чтобы использовать некоторые специальные инструкции, которые позволяют обновлять 4 пикселя экрана всего за 5 машинных циклов. В этом нет ничего особенно фантастического, и он подробно описан в IIgs Tech Note # 70 . Основным моментом было то, как я должен был организовать код, чтобы сделать его достаточно гибким, чтобы стать библиотекой общего назначения при сохранении максимальной скорости.
Я разложил графический экран на строки сканирования и создал 246-байтовый буфер кода для вставки специализированных 65816 кодов операций. Требуются 246 байтов, потому что каждая строка сканирования графического экрана имеет ширину 80 слов, и для дополнительного плавного перемещения требуется 1 дополнительное слово на каждом конце. Команда Push Effective Address (PEA) занимает 3 байта, поэтому 3 * (80 + 1 + 1) = 246 байтов.
Графический экран отображается при переходе по адресу в буфере 246-байтового кода, который соответствует правому краю экрана, и добавлению в инструкцию BRanch Always (BRA) кода в коде слова, следующего сразу за крайним левым. слово. Инструкция BRA принимает 8-битное смещение со знаком в качестве аргумента, поэтому она едва имеет диапазон для выхода из буфера кода.
Даже это не так уж сложно, но здесь идет настоящая оптимизация ядра. Мой графический движок фактически поддерживал два независимых фоновых слоя и анимированные листы, используя разные 3-байтовые последовательности кода в зависимости от режима:
- Фон 1 использует инструкцию Push Effective Address (PEA)
- Фон 2 использует инструкцию Индексации с косвенной загрузкой (LDA ($ 00), y), за которой следует push (PHA)
- Анимированные листы используют индексированную страницу загрузки с прямой индексацией (LDA $ 00, x), за которой следует push (PHA)
Критическим ограничением является то, что оба регистра 65816 (X и Y) используются для ссылки на данные и не могут быть изменены. Кроме того, регистр прямой страницы (D) устанавливается на основе происхождения второго фона и не может быть изменен; регистр банка данных установлен в банк данных, который содержит данные пикселей для второго фона и не может быть изменен; указатель стека (S) отображается на графическом экране, поэтому нет возможности перейти к подпрограмме и вернуться.
Учитывая эти ограничения, мне нужно было быстро обрабатывать случаи, когда слово, которое собирается поместить в стек, смешано, то есть половина приходит из фона 1, а половина из фона 2. Мое решение было обменять память на скорость. Поскольку все обычных регистров использовались, у меня был только регистр счетчика программ (ПК) для работы. Мое решение было следующим:
- Определить фрагмент кода для смешивания в том же банке программ 64K, что и буфер кода
- Создать копию этого кода для каждого из 82 слов
- Существует соответствие 1-1, поэтому возвращаемый фрагмент кода может быть жестко закодированным адресом
- Готово! У нас есть жестко запрограммированная подпрограмма, которая не влияет на регистры процессора.
Вот фактические фрагменты кода
code_buff: PEA $0000 ; rightmost word (16-bits = 4 pixels)
PEA $0000 ; background 1
PEA $0000 ; background 1
PEA $0000 ; background 1
LDA (72),y ; background 2
PHA
LDA (70),y ; background 2
PHA
JMP word_68 ; mix the data
word_68_rtn: PEA $0000 ; more background 1
...
PEA $0000
BRA *+40 ; patched exit code
...
word_68: LDA (68),y ; load data for background 2
AND #$00FF ; mask
ORA #$AB00 ; blend with data from background 1
PHA
JMP word_68_rtn ; jump back
word_66: LDA (66),y
...
Конечным результатом стал почти оптимальный блитер, который имеет минимальные издержки и запускает более 15 кадров в секунду при скорости 320x200 при использовании процессора 2,5 МГц с шиной памяти 1 МБ / с.