Абзац
:: Поиск
:: Поддержка проекта
Webmoney:
  • Z610389805629
  • R427996570517
  • E023541002978
  • :: №30 (01.04.2007) Просмотров: 4537

    Автор: Виталий Гаврилов / 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"   ;исходная картинка



    © 2004-2013 Perspective group