![]() |
|
:: СОДЕРЖАНИЕ НОМЕРА
:: Газетные рубрики
:: АВТОРЫ
:: Поиск
:: Поддержка проекта
Webmoney:
|
:: №30 (01.04.2007) Просмотров: 4955
Автор: Виталий Гаврилов / Vitamin. Рубрика: Читатель читателю. Номер: №30 (01.04.2007). Быстрый вывод графикиНаверное, каждый программист на Спектруме сталкивался с проблемой нехватки скорости. В частности, скорости переноса графической информации в видеопамять. Обычно, все упирается в довольно большую «прожорливость» команд копирования данных. И эффект медленной работы проявляется визуально самыми различными способами- от «бегущей линии» рассинхронизации, когда развертка растра «догоняет» указатель вывода в видеопамять, до весьма чувствительного понижения числа кадров в секунду и мерцания.Так уж сложилось, что решить данную проблему аппаратно практически невозможно (турбирование не в счет), а посему ее приходится решать программными методами. Постепенная эволюция методов вывода от простого LD A,(HL):LD (DE),A через LDIR/LDI к стековому выводу. Каждый метод имеет свою область применения, свои достоинства и недостатки. Сегодня поговорим о стековом выводе полноэкранных статических изображений. Метод копирования (заполнения) области памяти, используя стековые операции, имеет в основе простой факт - процедуры PUSH rp/POP rp позволяют записать/считать сразу 2 байта за 11/10 тактов. Т.е. в среднем 5 тактов на байт. Весьма существенная разница по сравнению с 17 тактами на байт одного цикла LDIR. Но за все приходится платить. В частности, увеличенным размером кода (чтоб вывести 2 байта данных надо 4 байта кода) и повышенной сложностью подготовки данных. Обычно используются два метода стекового вывода: 1) POP HL LD (...),HL Требующий 26 тактов и 4 байта памяти для вывода двух байт. Обычно используется в скроллерах, поскольку позволяет достаточно гибко менять адрес источника, периодически перезагружая SP. 2) LD rp,... PUSH rp Требующий 21 такт и 4 байта памяти для вывода двух байт. Слегка модифицированный вариант: 2а) LD HL,(...) PUSH HL Требует уже 27 тактов и 4 байта, но имеет сравнимую с методом 1 гибкость. Итого, наименее гибкий метод оказался самым быстрым- 10.5 тактов на копирование одного байта. Поскольку перед нами стоит задача максимально быстро перебросить статическую картинку, то в итоге можно остановиться на этом методе. Программа-выводилка будет выглядеть как цепочка LD rp,... PUSH rp LD rp,... PUSH rp ... Будем считать, что у нас имеется некоторая функция-кодогенератор. Она будет создавать вышеуказанный код, выполнение которого как раз будет приводить к выводу картинки в видеопамять. Проведя поверхностный анализ такого кода, можно заметить достаточную избыточность цепочек типа: LD rp,#ff00
PUSH rp
LD rp,#ffaa
PUSH rp
LD rp,#ffaa
PUSH rp
LD rp,#01aa
PUSH rpВнимательный взгляд программиста позволит соптимизировать его до конструкции: LD rp,#ff00
PUSH rp
LD rl,#aa
PUSH rp
PUSH rp
LD rl,rh
LD rh,#01
PUSH HLИтого вместо 84 тактов и 16 байт кода для вывода 8 байт данных мы имеем 11+10+7+11+ +11+4+7+11=72 такта и 12 байт кода. 15% прирост скорости даже на таком вот небольшом участке! Такой метод оптимизированного вывода был опубликован в ZXGuideTrash и показал весьма хорошие результаты. А если еще и заменить многократную загрузку числа в регистр на однократную загрузку этого числа в промежуточный регистр с дальнейшей загрузкой в выводящий регистр, то можно еще больше увеличить скорость вывода! Приведенная ниже программа выполняет именно такую оптимизацию. В исходном массиве данных выбираются четыре наиболее частых байта для последующей загрузки их в выводящий регистр. Немного доработав программу, можно попытаться увеличить их число. Дальшейшая оптимизация кодогенератора может свестись к поиску локальных пиков статистического распределения байт, но это является далеко не тривиальной задачей. Область применения такой выводилки можно вкратце очертить так: - быстрое восстановление статического фона. Можно применять в том случае, если имеется множество объектов, запоминать и восстанавливать фон под которыми весьма и весьма накладно. - полноэкранная анимация с небольшим числом кадров. При этом кадры могут создаваться непосредственно перед вызовом кодогенератора для вывода. Основные тонкости и принципы работы кодогенератора прописаны в комментариях к исходному тексту. Используется синтаксис Alasm: 'label - старший байт метки label. FASTPUT=#C000 ;где будет создаваться выводилка (#XX00)
LEN=6912 ;сколько будем выводить
ORG 24576
CALL MKFASTPUT ;генерация выводилки
LOOP HALT ;пробный цикл для показа времени вывода
LD A,2 ;заполнение бордюра красным цветом
OUT (-2),A ;будет показывать затрачиваемое на вывод
CALL FASTPUT ;время (примерно)
XOR A ;если бордюр будет мерцать, значит вывод
OUT (-2),A ;не укладывается в прерывание
IN A,(-2)
RRA
JR C,LOOP
RET
MKFASTPUT LD DE,BACKGR ;генерация выводилки
LD HL,FASTPUT ;используем первые 512 байт места
FLL1 LD (HL),E ;под выводилку для таблицы
INC L ;частот байтов картинки
JR NZ,FLL1
INC H
LD A,H
CP 'FASTPUT+2
JR NZ,FLL1
LD H,'FASTPUT ;и считаем количество байтов
LD B,'LEN ;каждого вида
CNT1 LD A,(DE)
LD L,A
INC (HL)
JR NZ,NXR1 ;2 байта на счетчик
INC H
INC (HL)
DEC H
NXR1 INC E
JR NZ,CNT1
INC D
DJNZ CNT1
CALL FINDMX ;ищем самый частый байт
LD (FRQB1),A ;и сохраняем его в шаблон и
LD (FRQB1_),A ;процедуру поиска
CALL FINDMX ;и так еще 3 раза
LD (FRQB2),A ;итого мы имеем 4 самых частых
LD (FRQB2_),A ;байта, которые будут храниться
CALL FINDMX ;в регистрах
LD (FRQB3),A
LD (FRQB3_),A
CALL FINDMX
LD (FRQB4),A
LD (FRQB4_),A
FSTMAK
LD HL,FASTPUTTMPL ;копируем начало шаблона
LD DE,FASTPUT
LD BC,ENDTMPL-FASTPUTTMPL
LDIR
EX DE,HL
LD IX,BACKGR+LEN-2 ;по исходной картинке идем с
LD BC,LEN/2 ;конца за LEN/2 шагов
LD DE,(BACKGR+LEN-2);первые два байта картинки -
PUSH BC ;загрузка прямо в HL
JR STORRA ;и переход на генерацию LD HL,NN
MKCYC PUSH BC ;цикл генерации кода
LD C,0 ;индекс регистра
LD A,(IX+1) ;старший байт в паре
CALL SEARVAR ;ищем индекс
LD A,C
RLCA
RLCA
RLCA
RLCA
LD C,A ;заносим в старшие 4 бита С
LD A,(IX) ;младший байт в паре
CALL SEARVAR ;ищем (С инкрементируется!)
LD A,C
AND #F0 ;старший байт в Н уже есть?
JR NZ,NO_HH
LD A,C ;да
DEC A
JR Z,STRPS ;игнорируем LD H,H:LD L,L
LD A,E
CP (IX) ;младший байт уже есть?
JR Z,STRPS ;да
JR COMM_1 ;нет - создаем команду
NO_HH XOR C ;изменился старший байт
JR NZ,NO_PDX ;пытаемся обойти коллизию
XOR C ;LD H,..:LD L,H
RRA
RRA
RRA
RRA
LD (HL),#6C ;код LD L,H:
INC HL ;генерация LD H,R
CALL STORCM ;ниже объясняются эта последо-
CP #26 ;вательность действий
JR NZ,STORCOM
LD (HL),A
INC HL
LD A,(IX+1)
JR STORCOM
NO_PDX LD A,C ;если оба байта не в списке
CP #66
JR NZ,COMMON
STORRA LD (HL),#21 ;код LD HL,NN
INC HL
LD A,(IX) ;подгрузка данных
LD (HL),A
INC HL
LD A,(IX+1)
JR STORCOM
COMMON AND #F0 ;хотя бы один байт в регистрах
RRA
RRA
RRA
RRA
CALL STORCM ;берем код загрузки старшего
LD (HL),A ;байта
INC HL
CP #26 ;это LD H,N?
JR NZ,COMM_1
LD A,(IX+1) ;если да, то еще один операнд
LD (HL),A
INC HL
COMM_1 LD A,C ;берем младший операнд
AND #0F
CP 1
JR Z,STRPS ;не изменился!
CALL STORCM ;иначе берем код
OR 8 ;меняем LD H,.. -> LD L,..
CP #2E ;это LD L,N?
JR NZ,STORCOM
LD (HL),A ;если да, то еще один операнд
INC HL
LD A,(IX)
STORCOM LD (HL),A
INC HL
STRPS LD (HL),#E5 ;сохраняем код PUSH HL
INC HL
LD E,(IX) ;DE содержит текущие данные
LD D,(IX+1) ;которые будут в HL во время
DEC IX ;выполнения. Там они будут под-
DEC IX ;ставляться, а тут можно сразу
POP BC ;получить их из входного потока
DEC BC
LD A,B
OR C
JP NZ,MKCYC ;цикл...
LD DE,TMPLEND
LD BC,TMPLENDA-TMPLEND
EX DE,HL
LDIR ;копируем завершающий фрагмент
RET ;из шаблона
CTAB LD H,H ;Таблица для выборки команды по индексу
LD H,L
LD H,D
LD H,E
LD H,B
LD H,C
DB #26 ;LD H,N, для экономии 1 байта
FINDMX LD DE,0 ;поиск байта с максимальной частотой
LD HL,FASTPUT+256
SEARMS LD A,(HL)
CP D
JR C,LESS
DEC H
LD A,(HL)
INC H
CP E
JR C,LESS
LD E,A
LD D,(HL)
LD C,L
LESS INC L
JR NZ,SEARMS
LD L,C
LD (HL),0 ;и обнуление его счетчика
DEC H
LD (HL),0
LD A,C
RET
STORCM PUSH HL ;выборка кода по индексу
ADD A,CTAB
LD L,A
ADD A,'CTAB
SUB L
LD H,A
LD A,(HL)
POP HL
RET
SEARVAR ;поиск переменной по значению
CP D ;H,L,D,E,B,C,N - регистр
RET Z ;0,1,2,3,4,5,6 - индекс
SEARVR INC C
CP E
RET Z
INC C
FRQB2_ EQU $+1
CP 0
RET Z
INC C
FRQB1_ EQU $+1
CP -1
RET Z
INC C
FRQB4_ EQU $+1
CP #55
RET Z
INC C
FRQB3_ EQU $+1
CP #AA
RET Z
INC C
RET
TMPSP DW 0 ;переменная для хранения SP
FASTPUTTMPL LD (TMPSP),SP ;шаблон для выводилки. Можно подправить
DI ;для своих целей
LD A,(SCRADD)
OR #40+'LEN
LD H,A
LD L,0
LD SP,HL
FRQB1 EQU $+1
FRQB2 EQU $+2
LD DE,#00FF
FRQB3 EQU $+1
FRQB4 EQU $+2
LD BC,#55AA
ENDTMPL
TMPLEND LD SP,(TMPSP) ;завершение шаблона
EI
RET
TMPLENDA
SCRADD DB 0 ;смещение старшего адреса. 0 или 128
BACKGR INCBIN "FTFuture" ;исходная картинка |