Art Testowy #1

The Fastest Plot Routine, czyli krótki wywód na temat robienia kropek...

Motto: "Cel uświęca środki" (N.M.)

Yo!

Jak wie większość koderów (a także część lamerów) w wielu efekcikach wykorzystuje się procedurki rysujące punkty. Ich rodzajów jest bez liku - od najbardziej wolnych i beznadziejnych po krótkie, szybkie i błyskotliwe. Skoncentruję się na tych drugich, temat pierwszych, zaś pozostawię autorytetom w tej dziedzinie (Yo Konrad K., twoje „routiny” są najlepsze z... najgorszych!!!). Na samym początku chciałbym zaznaczyć, że artykuł ten będzie dotyczył tylko i wyłącznie procedurek stawiania plota na ekranie w trybie graficznym, przy podaniu pozycji X i Y w odpowiednich rejestrach.

Na pierwszy ogień pójdzie procedurka autorstwa BARTMANA z SCG. Routin ten wykonuje się 35 cykli. Jest on użyty w części "Plot Master" dema "Sweet Illusions" (jeśli go nie masz – Twój błąd). Procedurka ta jest OK, ale i tak dosyć dziwnie stawia pixela (jako pierwszy rozkaz ADC?). Ale i tak zajmuje trzecie miejsce co do szybkości. Niestety nie przytoczę tu listingu tej procedurki, gdyż po prostu następne będą lepsze (Bartman! - Twoja chęć brania wszystkiego do buzi napawa mnie wstrętem!).

Tak się właśnie zastanawiam, czy wszyscy z Was wiedzą, co taka procedurka PLOT robi??? Najprostsza odpowiedź – znajduje w pamięci odpowiedni bajt i wpisuje tam odpowiednią liczbę. Proste, nie? Pewnie, jak rogalik (francuski, of course!).

Teraz troszkę jaśniej, krok po kroku, czynności wykonywane przez PLOT.

1. Bazując na danej Y znajdź adres początku linii, w której będzie się znajdował pixel. W assie wygląda to tak:

    LDA LtabLo,y
    STA ADRES      - kom. na stronie zerowej
    LDA LtabHi,y
    STA ADRES+1

W tabeli LtabLo i LtabHi zapisane są po kolei adresy wszystkich linii (odpowiednio młodszy i starszy bajt). Sposób uzyskania tych tabel (i innych) na końcu tego porąbanego tekstu.

2. Dodanie do tego adresu liczby, oznaczającej, w którym bajcie danej linii znajduje się pixel. Dla oszczędzenia sobie (i CPU) czasu zakładamy, że używamy zwężonego ekranu ($22F; 21).

    LDA ADRES
    CLC
    ADC XPOZ,X
    STA ADRES
    BCC *+4
    INC ADRES+1

W tabeli XPOZ znajdują się liczby w ciągu:

    00 00 00 00 00 00 00 00
    01 01 01 01 01 01 01 01
    02 02 02 ... itd.

3. Odczytanie z wyniku bajtu, zapalenie w nim bitu odpowiadającego danemu pixelowi i zapisanie bajtu.

    LDY #0
    LDA (ADRES),Y
    ORA BITTAB,X
    STA (ADRES),Y

W tabeli BITTAB znajdują się liczby, w których zapalony jest tylko jeden, określony bit:

    $80 $40 $20 $10 $08 $04 $02 $01
    $80 $40 $20 $10 $08 $04 $02 $01... itd.

Oto i cała procedurka. Jednak w tej postaci zajmuje ona aż 31 bajtów i wykonuje się troszkę długo.

Wielu ludzi próbowało zrobić to szybciej i krócej (np. wymieniony wcześniej Bartman). Procedurka ewoluowała i ostatecznie w jednym z "Mega Magazine'ów" (dokładnie w wydaniu IV w artykule Detaila "Fast Plotter Routines" - przyp.red.) ktoś napisał, że jego procedurka jest najszybsza i że nie da się tego zrobić lepiej. Rzeczywiście procedurka ta jest bardzo szybka i krótka, ale dokonałem niemożliwego - przyspieszyłem co nie daje się przyspieszyć!

Ale, powoli, najpierw przyjrzyjmy się procedurce z "MegaZine'u"...

    Mnemonik        Bajty   Cykle
    LDA LTABLO,Y    3       4
    STA ADRES       2       3
    LDA LTABHI,Y    3       4
    STA ADRES+1     2       3
    LDY XPOZ,X      3       4
    LDA (ADRES),Y   2       5
    ORA BITTAB,X    3       4
    STA (ADRES),Y   2       6
                  ---     ---
                   20      33

Tak więc pobito Bartmana (o 2 cykle!). Teraz w jaki sposób przyspieszyłem tego routina...

We wszystkich znanych mi PLOTach koderzy wpisują coś do komórki ADRES (Lo byte). Z powodu braku rozkazu procesora 6502 (6502C posiada) LDA (pag0) i STA (pag0) trzeba wykorzystywać indeksowanie rejestrem Y. W związku z tym częste są rozkazy np. LDY #0. Ostatecznie, młodszy bajt adresu zawsze jest sumą kom. ADRES i rejestru Y. Wydaje się, że bajt starszy zależy od znacznika C wyniku tego dodawania. Ale przyjrzyjmy się danym z naszych tablic. W tabeli LTABLO ważne są (przy ekranie 32 bajty) tylko najstarsze 3 bity (%11100000). Natomiast w XPOZ liczą się tylko najmłodsze 5 bitów (%00011111). W związku z tym dodawanie dwóch danych z tych tablic nigdy nie ustawi C !!! Dlatego wpadłem na pomysł, aby do rejestru Y wpisywać sumę tych danych, a w komórce ADRES cały czas mieć ustawione 0. Kiedy jednak zrobiłem PLOTa tym sposobem nadal był on jeszcze wolniejszy od tego z "MegaZine'u" (34 cykle), gdyż był tam rozkaz CLC przed dodawaniem. Nie musiał on być co prawda zawsze ustawiany, ale w końcu ma to być PLOT uniwersalny. Po paru sekundach namysłu wyrzuciłem zarówno CLC jak i ADC i zastąpiłem to starym, dobrym rozkazem ORA (bity się nie pokrywają, więc wynik ten sam co przy ADC)!!!

Dzięki temu moja procedurka wygląda tak:

    Mnemonik        Bajty   Cykle
    LDA LTABHI,Y    3       4
    STA ADRES+1     2       3
    LDA LTABLO,Y    3       4
    ORA XPOZ,X      3       4
    TAY             1       2
    LDA (ADRES),Y   2       5
    ORA BITTAB,X    3       4
    STA (ADRES),Y   2       6
                  ---     ---
                   19      32

Jak widać, procedurka ta jest szybsza o 1 cykl i krótsza o 1 bajt. Wiem, że 1 cykl to bardzo mało, i nie zdziwię się, jeśli ktoś przyspieszy również tą procedurkę.

Teraz jak przygotować tabele.

1. XPOZ

        LDY #0
    _1  TYA
        LSR @
        LSR @
        LSR @
        STA XPOZ,Y
        INY
        BNE _1

2. BITTAB

        LDY #0
        LDA #$80
    _2  STA BITTAB,Y
        LSR @
        BCC *+4
        LDA #$80
        INY
        BNE _2

3. LTABLO i LTABHI

        LDA <SCRMEM
        STA ZER1
        LDA >SCRMEM
        STA ZER1+1
        LDY #0
    _3  LDA ZER1+1
        STA LTABHI,Y
        LDA ZER1
        STA LTABLO,Y
        CLC
        ADC #$20
        STA ZER1
        BCC *+4
        INC ZER1+1
        INY
        BNE _3

To wszystko o uniwersalnym PLOT. Teraz moja (subiektywna) uwaga na temat przydatności takiej procedurki. Owszem, przy programach graficznych przyda się na pewno, ale przy pisaniu demosów, kiedy PLOT wywołuję się dziesiątki (setki) razy na ramkę, lepiej robić to wszystko w jakiejś chytrej pętli, żeby przy każdym wywołaniu PLOT nie trzeba było zapisywać rejestru, będącego znacznikiem w pętli, tak jak to zrobił Bartman. Przykładem tego jest mój ploterek w demie "SHAKE!".

That's all folx! See Ya!!!
Little Beatle

P.S. Uwaga! W tym miejscu mały konqrs! Pytanie brzmi: Jaka atarowska (polska) grupa tańczy w jednej z piosenek z płyty "Dirt" grupy Alice in Chains?

Pierwsza osoba, która nadeśle odpowiedź na mój adres (patrz jakiś mój demos lub spytaj znajomych) otrzyma gratisową dyskietkę z nagranymi demosami członków grupy APC, wraz z autografami. Kto wie, ile taki dysk będzie wart za 100 lat?!?

P.S.2 Yo Konop! W jaki sposób robisz PLOT-a w 18 cykli. Nie chce mi się w to jakoś wierzyć...

P.S.3 Dysk z zinem zawiera przykładowy plotter, bazujący na opisanej powyżej metodzie.