256 Bajtów - Króger


      W trzecim numerze Serious'a poruszona została sprawa 256 bajtówek. Stało się tak, gdyż pozostawiłem Jabolowi do dyspozycji mój pierwszy mikroefekcik. W swej formie pozostawiał nieco do życzenia, ale mógł posłużyć jako materiał dydaktyczny dla początkujących.

      Tym razem prezentuję programik znacznie doskonalszy, z dokładnym opisem technik zmierzających do dokonania maksymalnej optymalizacji pod względem objętości kodu.

      Kilka procedur składających się na poniższy efekcik (tworzenie tablicy sinusów, tworzenie DL, tworzenie tablic dla szybkiego plota) można uznać za wzorcowe, prawdopodobnie są najkrótsze. Możecie śmiało z nich korzystać.

      Efektem programiku zawierającego się w 264 bajtach jest wektorowa kula składająca się z 256 punktów obracająca się wokół dwóch osi. Zatem zaczynamy...

 *   TINY02
 * Króger/QMD

        opt %100101

 random equ $d20a

 tb1    equ $8000
 tbx    equ $3800
 tby    equ $3900
 tbz    equ $3a00
 bit    equ $3b00
 bajt   equ $3c00
 ekr    equ $1000

 pom1   equ $40
 mk     equ $42

      TB1 oznacza początek tablicy sinusów o tradycyjnej budowie, którą dokładnie opisałem w CA #1 Zine. Tablica zawiera wartości sinusów 64 kątów (zatem minimalny kąt obrotu wynosi 5.625 stopnia) pomnożone przez całkowite wartości z zakresu (-128, 127). Tablice TBX, TBY i TBZ zawierają współrzędne punktów tworzących kulę. Tablice BIT i BAJT służą do szybkiego stawiania plotów (jeśli nie wiesz o co chodzi,przeczytaj artykuł LBS'a z Barymaga #1). Pamięć obrazu zaczyna się od EKR (musi zaczynać się od początku 4kb bloku pamięci). Pozostałe zadeklarowane komórki spełniają funkcję pomocniczą.
        org $55


      Kod warto lokować na stronie zerowej. Ma to sens, gdy kod programu sam się modyfikuje. W normalnym wypadku zmuszeni bylibyśmy do korzystania z absolutnego trybu adresowania (adres dwubajtowy), jeśli kod znajduje się na stronie zerowej możemy korzystać z trybu zerostronicowego (adres zawarty w jednym bajcie).

      Rozwiązanie to posiada jednak szereg wad. Po pierwsze trudno jest ustalić jaką częścią strony zerowej możemy używać. Przyjmuje się, że dostępna jest tylko starsza połowa strony zerowej. Jednak niekoniecznie - zależy to od używanego DOS'u. Najbardziej rozpowszechniony II+D ingeruje w stronę zerową tylko do $54, reszta należy do nas, dlatego kod możemy umieścić śmiało od $55, należy jednak pamiętać, że programik niekoniecznie musi działać pod wszystkimi DOS'ami.

      Program przekraczający długość $aa ($ff-$55) załaduję się na początek stosu. Można to śmiało zrobić, gdyż system korzysta z niewielkiej części stosu (ok. 40 ostatnich bajtów). Jednak zajęcie części stosu może wejść w kolizję z innymi programami zeń korzystającymi (np. turbo stacji). Należy się z tym liczyć.

 d0 equ *
    ldy #0
    sty mk
    tya
    pha
    ldx #$ff
 d1 equ *
    lda mk
 d2 adc si+31
    sta mk
    pla
    adc #0
    pha

 d3 sta tb1+$1f00,y
 d4 sta tb1+$3f00,x
    eor #$ff
 d5 sta tb1+$1f00,x
 d6 sta tb1+$3f00,y
    dex
    iny
    bpl d1

    pla
    dec d2+1
    dec d3+2
    dec d4+2
    dec d6+2
    dec d5+2

    bmi d0

      Powyższa procedura tworzy tradycyjną tablicę sinusów o zakresie argumentów (-128,127). Korzysta ona z tablicy SI zawierającej sinusy kątów:

            0, 5.625, 11.25, ... ,84.375, 90, 84.375, 78.75, ... ,11.25, 5.625

Wartości te są pomnożone przez 256. Nasza procedurka kolejno (dla każdego argumentu) dodaje wartość pobraną z tablicy SI i zapisuje ją w słowie, którego młodszy bajt zawiera się w MK, a starszy (zapisywany do tworzonej tablicy przetrzymywany jest na szczycie stosu. Dla oszczędności zrezygnowałem z rozkazu CLC przed dodawaniem.

      Zrozumienie działania procedury nie jest konieczne dla zrozumienia dalszej części artykułu. Problem tworzenia tablic sinusów poruszyłem swego czasu w CA Zine #1 i zainteresowanych tamże odsyłam.

UWAGA! Rozmiar tablicy (64 kąty) jest sztywny - nie można go zmieniać.

 sin equ d3+1
 cos equ d4+1

      SIN i COS to słowa w których znajdują się informacje wykorzystywane później przy adresowaniu postindeksowanym pośrednim w celu pobrania danych z tablicy sinusów. W słowach tych młodszy bajt musi być zawsze równy 0. Aby programowo nie wpisywać do jakichś komórek wartości 0, możemy spokojnie wykorzystać te, w których 0 na pewno się znajduje, a są to D3+1 i D4+1, gdyż takie wartości są tam ładowane wraz z kodem programu.

      Teraz zajmiemy się naszym display list. Cały efekt będzie wyświetlany tylko na jednej płaszczyżnie ekranu (nie stać nas na rozrzutność), skutkiem tego będzie problem nieobcy żadnemu koderowi, czyli "mruganie" (brak synchronizacji z Antic'em).

    lda #15
 l0 equ *
    sta dl2-1,x
    dex
    bne l0

    lda <dl1
    sta $230
    lda #33
    sta 559

      Powyższa pętelka tworzy 127 linii trybu $f Antica, umieszczone za trzema bajtami znajdującymi się od DL1 tworzą display list efektu. Dla oszczędności miejsca zrezygnowałem z końcowych danych DL, czyli LMSu ze skokiem i jego adresem. Jeżeli DL jest jednorodny (bez skoków) i przerwania systemowe są wyłączone z adresu możemy zrezygnować zawsze pozostawiając jedynie LMS ze skokiem (64+1). Z niego również zrezygnowałem, co może powodować, że pod ekranem będą wyświetlane jakieś śmieci. Zawartość stosu za DL jest jednak zazwyczaj "nieszkodliwa" i kasza pod ekranem jest raczej nieznaczna.

      Ostatnie cztery rozkazy ustalają szerokość ekranu i podają młodszy bajt DL (starszy będzie podany przy sprzyjającym stanie akumulatora). Znajomość bieżącego stanu rejestrów pozwala uniknąć zbędnego wpisywania do nich wartości już tam się znajdujących, co zostanie wykorzystane również w poniższych procedurkach:

 c0 equ *
    tya
 c1 equ *
    sta bit,x
    inx
    beq c2
    lsr @
    bcc c1
    bcs c0

Ta zapisuje stronę pamięci zaczynającą się od BIT 32 blokami pamięci o postaci:

            128, 64, 32, 16, 8, 4, 2, 1

 c2 equ *
    txa
    lsr @
    lsr @
    lsr @
    sta bajt,x
    inx
    bne c2

Ta zapisuje stronę pamięci zaczynającą się od BAJT wartościami od 0 do $1f, przy czym każda z nich jest umieszczana ośmiokrotnie tworząc jeden blok.

Obie tablice służą do przetwarzania współrzędnej X na punkt na ekranie. Z tablic korzysta się następująco:

  1. Do rejestru indeksującego załadować współrzędną X punktu.
  2. Z tablicy BAJT pobieramy (indeksując) numer bajtu w linii, w którym będzie znajdował się punkt.
  3. Z tablicy BIT pobieramy (indeksując jak poprzednio) maskę punktu.
Zresztą każdy wie o co chodzi.
 a0 equ *
    jsr los
    ldy #60
    lda (sin),y
    sta tbz,x
    lda (cos),y
    tay

    jsr los
    lda (cos),y
    sta tbx,x
    lda (sin),y
    sta tby,x

    inx
    bne a0

      Powyższa procedurka tworzy powierzchnię kuli złożoną z 256 punktów. Jej działanie polega na obróceniu o losowe kąty (kąty losuje podprogram LOS, którego działanie będzie opisane niżej) punktu o współrzędnych 60, 0, 0 wokół osi Y i Z. Algorytm obrotu został w skrajnym stopniu skrócony. Możemy się temu przyjrzeć wychodząc od podstawowych wzorów na obrót wokół osi Y i Z (sprawę obrotów dokładnie opisałem w CA Zine #1):
    x1=cos(alfa)*x-sin(alfa)*z
    y1=y
    z1=sin(alfa)*x+cos(alfa)*z

    x2=cos(beta)*x1-sin(beta)*y1
    y2=sin(beta)*x1+cos(beta)*y1
    z2=z1

      Zauważmy, że z i y1 (y1=y) są zawsze równe 0, dlatego drugi składnik obu sum i odjemniki obu różnic mogą być pominięte. Dzięki temu powyższe wzory możemy skrócić do postaci:
    x1=cos(alfa)*x
    y1=y
    z1=sin(alfa)*x

    x2=cos(beta)*x1
    y2=sin(beta)*x1
    z2=z1

I powyższa procedurka jest właśnie przełożeniem tych wzorków.

      Po tych przygotowaniach wstępnych czas na pętlę główną efektu. Jej układ może być zaskakujący. Najpierw jest stawiany punkt (cztery rozkazy zaczynając od E3), a następnie jest wyliczany kolejny punkt, który będzie postawiony dopiero, gdy pętla rozpocznie swój bieg od początku.

Skutkiem tego pierwszy punkt, który będzie postawiony (numer punktu przechowywany jest w rejestrze X ) został wyliczony jeszcze w poprzedniej klatce. Cute!

      Na początek trzy czynności wstępne, które muszą być wykonane przed rozpoczęciem rysowania klatki.

 e0 equ *
    lda 20
    cmp 20
    beq *-2

Synchronizacja...
    ldy >ekr
    sty e1+2
    txa
 e1 sta ekr,x
    inx
    bne e1
    inc e1+2
    dey
    bpl e1

...czyszczenie ekranu...
    lda sin+1
    jsr loz

...wpisanie do SIN+1 i COS+1 pozycji odpowiedniego miejsca z tablicy sinusów (określającej kąt obrotu).
 e3 ldy bajt
 e4 lda bit
    ora (pom1),y
    sta (pom1),y

    lda #1
    sta pom1
    sta $231

    ldy tbz,x
    lda (sin),y
    ldy tby,x
    sbc (cos),y
    ora #$80
    lsr @
    ror pom1
    lsr @
    ror pom1
    lsr @
    ror pom1
    sta pom1+1

    lda (sin),y
    ldy tbz,x
    adc (cos),y
    tay

    lda (sin),y
    ldy tbx,x
    sbc (cos),y
    sta e3+1
    sta e4+1

    inx
    bne e3

    inc sin+1
    bne e0

      W pierwszych czterech liniach petli stawiany jest punkt (E3+1 i E4+1 są modyfikowane). Następnie zerowane jest POM1 poprzez wstawienie wartości 1 (bity 0-2 i tak giną poprzez potrójne LSR).

      Następnie wykonywane są obroty wokół osi X i Y według wzorów, które najpierw obejrzmy w wersji oryginalnej (pełnej):

    x1=x
    y1=cos(alfa)*y-sin(alfa)*z
    z1=sin(alfa)*y+cos(alfa)*z

    x2=cos(beta)*x1-sin(beta)*z1
    y2=y1
    z2=sin(beta)*x1+cos(beta)*z1

A tak wygląda skrócona postać tych wzorów, z której skorzystałem:
    x1=x
    y1=sin(alfa)*z-cos(alfa)*y
    z1=sin(alfa)*y+cos(alfa)*z

    x2=cos(alfa)*x1-sin(alfa)*z1

W algorytmie dokonałem trzech uproszczeń:
  • kula jest obracana wokół obu osi o ten sam kąt (alfa)
  • zrezygnowałem z wylicznia Z2, gdyż dana ta nie jest użyteczna
  • przy wyliczniu Y1 zamieniłem miejscami odjemną z odjemnikiem, skutki tego są regulowane przez podanie Antic'owi początku pamięci ekranu przesuniętej o $800 - skutkiem tego, górna półówka będzie wyświetlana na dole i odwrotnie (należy mieć na uwadze, że licznik pamięci Antic'a jest 12-bitowy (adresuje tylko zakres $0000-$3fff).
      Wyliczone Y1 jest mnożone przez 32 (każda linia ekranowa liczy sobie 32 bajty) i przesuwane na początek pamięci ekranu ($1000). Przy odejmowaniach i dodawaniach zrezygnowałem z rozkozów CLC i SEC.

      Wyliczony X2 nie musi być ześrodkowywany (tzn. nie trzeba doń dodawać 128, czyli połowy szerokości ekranu wyrażonej w liczbie pikseli), robi to za nas Antic, który pamięć obrazu przesuwa o 16 bajtów (spójrz niżej, pod etykietę DL1).

      Kula obraca się o minimalny zdefiniowany kąt (INC SIN+1), wyjście poza zakres (czyli ustalenie kątu nr 64) jest póżniej korygowane przez odwołanie się do procedurki LOZ.

 los equ *
     lda random
     ora #$80
 loz equ *
     and #%10111111
     sta sin+1
     adc #16
     and #%10111111
     sta cos+1
     rts

      Dzięki powyższej procedurce bity 0-5 SIN+1 otrzymują wartość losową, bit 7 zostaje ustawiony, a bit 6 zostaje skasowany. Zatem wartość SIN+1 mieści się w zakresie $80-$bf. Słowo SIN zawiera adres fragmentu tablicy sinusów dotyczącego wybranego kąta, a tablica sinusów jest umieszczona od $8000 i sięga aż do $bf00.

      COS+1 różni się od SIN+1 tylko sześcioma młodszymi bitami. Dodaje się do nich 16, a na wypadek, gdyby nastąpiło przeniesienie do bitu 6, jest on zerowany. Jest to zrozumiałe, gdyż:

    sin(alfa)=cos(alfa+90)
    360=64*5.625
     90=16*5.625

A naszą tablicę składają się sinusy 64 kątów - 32 pobrane z SI, oraz ich negacje. Druga połowa tablicy sinusów jest negacją pierwszej, gdyż:
    sin(alfa)=-sin(alfa+180)

Ale o tym wszystkim już kiedyś pisałem
 si  equ *
     dta b(0),b(25),b(50),b(74)
     dta b(98),b(121),b(142),b(162)
     dta b(181),b(198),b(213),b(226)
     dta b(237),b(245),b(251),b(254)
     dta b(255),b(254),b(251),b(245)
     dta b(237),b(226),b(213),b(198)
     dta b(181),b(162),b(142),b(121)
     dta b(98),b(74),b(50),b(25)

 dl1 equ *
     dta b(79),a(ekr+$800+16)
 dl2 equ *

      Jak widać display list jest umieszczony tuż za kodem, czyli znajduje się na obszarze stosu. Zwracam uwagę, że umiejscowienie DL jest sztywne i nie może być swobodnie zmieniane. Zwróćcie uwagę, w jaki sposób wpisywany jest adres DL pod $230-$231.
     end


      To tyle na powyższy temat, nie spodziewam się do niego wracać. Wszyscy chcący udoskonalić swój warsztat koderski mogą przyjrzeć się moim sztuczkom i śmiało je wykorzystywać.

      Jeszcze jedna sugestia z mojej strony. Zrobienie 256-tki polega przede wszystkim na znalezieniu i opracowaniu dobrego pomysłu. Najważniejsza jest orginalność. Dlatego proszę, przestańcie ludzi katować różnymi fajerkami i im podobnymi nowościami.

Króger/Quasimodos

PS. Ergo Bibamus "się robi" - dość powoli.

Errata do Serious #3 - programik "Bulki" nie był mojego autorstwa. Przypisał mi je błędnie ktoś z redakcji.

Od redakcji: Rzeczywiście, autorstwo publikowanego w SERIOUS #3 programu BULKI zostało błędnie przypisane Kógerowi. Ponieważ to JA ponoszę moralną odpowiedzialność za całe to zajście, Krógera oraz autora programu BULKI serdecznie przepraszam!

      Ponieważ Króger nie lubi się powtarzać, a w swoim artykule kilkukrotnie odwoływał się do stworzonych i opublikowanych wcześniej materiałów jeżeli będzie na nie zapotrzebowanie odpowiednie artykuły możemy jeszcze raz "przedrukować", na życzenie społeczeństwa, oczywiście...

Zbycho Jabol/DIAL