Недавно наш график нарушил работу одной из частей демо, добавив мультиколор в бордюре в центре экрана. Это сократило время, доступное для других эффектов, вдвое, так как половина кадра уходила на ожидание того момента, когда луч достигнет нужной позиции на экране. Но это огромная трата процессорного времени.
Именно поэтому я переписал эффекты (ничего особенного — просто вывод спрайтов на экран) как процедуры с фиксированным числом тактов (fixed-tacts).
Базовая процедура для рисования на экране — DOWNDE, которая вычисляет адрес следующей строки пикселей в видеопамяти. Типичная процедура выглядит примерно так:
DOWNDE inc d ;4T
ld a,d ;4T
and 7 ;7T
jr nz,DOWNDE1 ;12/7T
ld a,e ;4T
add a,32 ;7T
ld e,a ;4T
jr c,DOWNDE1 ;12/7T
ld a,d ;4T
sub 8 ;7T
ld d,a ;4T
DOWNDE1
;min: 27T, max: 59T
Два условных перехода. Возможно ли вообще переписать это как процедуру с постоянной временной сложностью? Давайте попробуем:
DOWNDE_T
inc d ;4T
ld a,d ;4T
and 7 ;7T
sub 1 ;7T
sbc a,a ;4T
and -8 ;7T
ld (DOWNDE_D+1),a ;13T
and 32 ;7T
add a,e ;4T
ld e,a ;4T
ccf ;4T
sbc a,a ;4T
DOWNDE_D and -8 ;7T
add a,d ;4T
ld d,a ;4T
;=84T
Вместо использования самомодифицирующегося кода мы могли бы пожертвовать одним из обычных регистров и сэкономить 12 тактов, но в таком виде процедура ведет себя точно так же, как и оригинал. А что насчет процедуры вывода спрайтов?
Процедура вывода спрайта очень проста. Помимо адреса данных и адреса в видеопамяти, она также ожидает адрес маски (я маскирую объекты на экране) и ширину спрайта (максимум 13 знакомест). Основной цикл выглядит так:
;вход: hl - данные спрайта
; de - адрес в видеопамяти
; bc - адрес маски
; c' - ширина спрайта
exx
ld b,c
S_LOOP1 exx ;4T
ld a,(bc) ;7T
and (hl) ;7T
ld (de),a ;7T
inc hl ;6T
inc c ;4T
inc e ;4T
exx ;4T
djnz S_LOOP1
;=43T
Чтобы гарантировать, что процедура занимает одинаковое количество тактов для спрайтов любой ширины, мы добавим еще один цикл, который расширяет время выполнения до времени отрисовки самого широкого спрайта. Один проход через новый цикл должен занимать 43 такта. Однако проблема здесь в том, что инструкция ld (de),a обращается к медленной памяти и не всегда занимает одинаковое количество тактов. Но инструкция ld a,(de) делает то же самое! Итак, давайте попробуем расширить нашу процедуру:
exx
ld b,c
S_LOOP1 exx ;4T
ld a,(bc) ;7T
and (hl) ;7T
ld (de),a ;7T
inc hl ;6T
inc c ;4T
inc e ;4T
exx ;4T
djnz S_LOOP1
ld a,13+1 ;нам не нравится 0 как счетчик цикла
sub c
ld b,a
S_LOOP2 exx ;4T
ld a,(bc) ;7T
and (hl) ;7T
ld a,(de) ;7T
ld iy,23610 ;14T
exx ;4T
djnz S_LOOP2
Занимает ли эта процедура каждый раз ровно столько же времени? Сомневаюсь. Но этого достаточно, чтобы синхронизировать стабильный мультиколор в бордюре. Инструкция ld a,(de) здесь важна; без нее у меня ничего не работало.
Желаю вам побольше процедур с фиксированным временем выполнения!
mike/zeroteam
Поделитесь вашим мнением о статье