Sekalaiset Sivukartta

Tuplapuskurointi
Markus Ketola

Tuplapuskurointi (tai kaksoispuskurointi, tässä käytetään nimitystä tuplapuskurointi (engl. double buffering)) on tekniikka, missä käytetään kahta bittikarttaa; toista näytetään, toiseen piirretään. Bittikarttojen funktion vaihto tehdään, kun näyttöruutua piirtävä elektronisuihku on kuvaruudun ala- tai ylälaidasssa. Näin saadaan aikaiseksi välkkymätön liike. Huomautettakoon vielä, että tämä juttu käsittelee tuplapuskuroinnin tekoa Classic Amigalla; tosin yhtä kaikki AmigaOnella tuplapuskuroinnin idea lienee sama.

Toteutustavat
Olen ottanut tähän artikkeliin mukaan kolme toteutusta: C-kielinen käyttöjärjestelmärutiineja käyttävä esimerkki, konekielinen käyttöjärjestelmärutiineja käyttävä esimerkki ja konekielinen suoraan erikoispiirejä käyttävä esimerkki eli ns. "hardiskoodausesimerkki". Jokaisessa näissä on tuplapuskuroinnin ohessa toteutettuna 3D-tähdet. 3D-tähtien toteutus ei ole mitenkään optimaalisen hyvin optimoitua koodia C-kielen puolella, mutta konekielipuolella koodi on 3D-tähtien osalta kohtuullisesti optimoitua. Tosin tuplapuskurointihan tässä pääasiana kuitenkin on. C-kielisestä samoin kuin konekielisestä käyttöjärjestelmärutiineja käyttävästä ohjelmasta poistutaan painamalla ESC:iä. Jälkimmäisen 3D-tähtirutiinit on otettu suoraan hardiskoodatusta ja hieman mukailtu tarkoitusta vastaaviksi. Hardiskoodatusta versiosta poistutaan painamalla hiiren vasempaa nappia. 3D-tähtirutiini on monta vuotta sitten tekemäni, jossa on käytetty hieman apuna taannoisessa C-lehdessä julkaistua listausta. Molemmat konekieliset ohjelmat on käännetty 68040:lle. Assemblerina on käytetty Aminetista löytyvää PhxAss-assemblerin versiota 4.39, mutta kääntämisen pitäisi onnistua helposti muillakin assemblereilla. Ohessa olevassa paketissa ovat ohjelmat kokonaisuudessaan sekä lähdekoodin lisäksi vielä ajettavat.

Toteutus käyttöjärjestelmärutiineilla C-kielisenä
Aluksi määritellään omat BitMap-struktuurit omia bittikarttoja varten, tämä tehdään seuraavasti:
    struct BitMap myBitMap0 = {...}
    struct BitMap myBitMap1 = {...}
BitMap-strukruutrin rakenne on seuraava:
    struct BitMap {
       UWORD BytesPerRow;
       UWORD Rows;
       UBYTE Flags;
       UBYTE Depth;
       UWORD pad;
       PLANEPTR Planes[8]; }
Näistä tarvitsee normaalisti välittää "BytesPerRow"- (tavuja per rivi), "Rows"- (rivejä), "Depth"- (syvyys eli bittitasojen määrä) ja "PLANEPTR"-kohdista. Määrittelyn yhteydessä PLANEPTR:eille asetetaan aluksi arvo NULL. Sitten määritellään omat bittikartat. Tämä tehdään määrittelemällä osoitinmuuttujat osoittamaan tyyppiä UBYTE:
    UBYTE *BM0,*BM1;
Seuraavaksi on aika varata (Chip-)muistia omia bittikarttoja varten. Tämä tehdään käyttäen AllocMem-rutiinia:
    BM0 = (UBYTE *)AllocMem(MÄÄRÄ,MEMF_CHIP|MEMF_CLEAR);
    BM1 = (UBYTE *)AllocMem(MÄÄRÄ,MEMF_CHIP|MEMF_CLEAR);
missä MÄÄRÄ = (näytön leveys pikseleinä / 8) * rivien määrä (huomaa, että C-kielessä muuttujien nimissä ei tosin voi olla skandinaavisia merkkejä). Nyt meillä on muistia varattuna omille bittikartoille, mutta omien BitMap-struktuuriemme PLANEPTR:it osoittavat NULL:iin, joten asettakaamme BitMap-struktuurimme kuntoon:
    myBitMap0.Planes[0] = &BM0[0];
    myBitMap0.Planes[1] = &BM0[BPL_KOKO];
    myBitMap0.Planes[2] = &BM0[2*BPL_KOKO];
    myBitMap0.Planes[3] = &BM0[3*BPL_KOKO];
    ...
Ja vastaava myBitMap1:lle. Seuraavaksi onkin jo aika avata oma näyttö ja ikkuna. Tehdään se OpenScreenTags- ja OpenWindowTags-rutiineilla. Ikkunan avaamisessa ei tarvitse huomioida mitenkään omia bittikarttoja, ja näytön avaamisessakin on vain pari tagia, jotka on huomioitava: SA_Type ja SA_BitMap. SA_Typen arvoksi asetetaan CUSTOMSCREEN|CUSTOMBITMAP (pystyviiva tarkoittaa or-operaatiota) ja SA_BitMap asetetaan osoittamaan toista omista BitMap-struktuureistamme, esim:
    SA_BitMap, &myBitMap1.
Näytön screen-struktuurista tarvitsemme jatkoa varten vielä ViewPort- ja RastPort-struktuurit, joten otetaan ne talteen seuraavaksi:
    rastport = window->RPort;
    viewport = &screen->ViewPort;
Yllä oletetaan, että alussa on ikkunaa avattaessa avaamisen tulos talletettu window-nimiseen Window-struktuuriin osoittavaan pointteriin sekä vastaavasti oletetaan, että näyttöä avattaessa avaamisen tulos on talletettu screen-nimiseen Screen-struktuuriin osoittavaan pointteriin. Ennen piirrettävän ja näytettävän bittikartan vaihtoa kutsutaan WaitTOF- tai WaitBOVP-rutiinia. Ja nyt puuttuukin enää rutiini, joka vaihtaa bittikarttamme elektronisuihkun ollessa näytön ala- tai ylälaidassa eli varsinainen tuplapuskurointirutiini. Tehdään se seuraavaksi:
    void DoubleBuffering(char *Which) {
       switch(*Which) {
          case 0 : viewport->RasInfo->BitMap=&myBitMap0;
                   rastport->BitMap=&myBitMap1;
                   *Which=1;
                   break;
          case 1 : viewport->RasInfo->BitMap=&myBitMap1;
                   rastport->BitMap=&myBitMap0;
                   *Which=0;
                   break;
       }
       ScrollVPort(viewport);
    }
Yllä päivitetään ViewPortin ja RastPortin BitMap-kentät ajan tasalle: Näytettävä bittikartta laitetaan ViewPort-struktuurin RasInfo-struktuurin BitMap-kenttään sekä piirrettävä bittikartta laitetaan RastPort-struktuurin BitMap-kenttään. Lisäksi, jotta ViewPort-struktuurin muutos tulee voimaan, on kutsuttava ScrollVPort-rutiinia, joka päivittää ViewPortin RasInfo-struktuurin tiedot copperlistaan.

Toteutus käyttöjärjestelmärutiineilla konekielisenä
Tässä käytetään LibCall-nimistä makroa, jonka määrittely selviää täydellisestä listauksesta. Aloitetaan määrittelemällä BitMap-struktuurit:
    myBitMap0  dc.w  40,256      ; Leveys tavuina, korkeus pikseleinä
               dc.b  0,3         ; Liput, Syvyys
               dc.w  0           ; Pad
               dc.l  BitPlane1_0
               dc.l  BitPlane2_0
               dc.l  BitPlane3_0
               dc.l  0
               dc.l  0
               dc.l  0
               dc.l  0
               dc.l  0

    myBitMap1  dc.w  40,256      ; Leveys tavuina, korkeus pikseleinä
               dc.b  0,3         ; Liput, Syvyys
               dc.w  0           ; Pad
               dc.l  BitPlane1_1
               dc.l  BitPlane2_1
               dc.l  BitPlane3_1
               dc.l  0
               dc.l  0
               dc.l  0
               dc.l  0
               dc.l  0
Bittitasoille voi varata muistia AllocMem-rutiinilla, mutta sen voi tehdä myös varaamalla Chip-muistista puskurin, ns. bss chunkin. Tehdään niin:
       section  Puskurit,bss,chip

    BitPlane1_0    ds.b  10240
    BitPlane2_0    ds.b  10240
    BitPlane3_0    ds.b  10240

    BitPlane1_1    ds.b  10240
    BitPlane2_1    ds.b  10240
    BitPlane3_1    ds.b  10240
NewScreen-struktuurissa täytyy näytön tyypiksi asettaa CUSTOMSCREEN!CUSTOMBITMAP (huutomerkki tarkoittaa or-operaatiota) sekä laittaa kenttään, joka viittaa käyttäjän määrittelemään BitMap-struktuuriin oman BitMap-struktuurin osoite. Otetaan tässä välissä talteen rastport- ja viewport-struktuurien osoitteet:
                   move.l   MinunIkkuna,a0          ; Otetaan ikkunan
                   move.l   wd_RPort(a0),rastport   ; rastport

                   move.l   MinunNaytto,a0          ; Otetaan näyton
                   lea      sc_ViewPort(a0),a0      ; viewport
                   move.l   a0,viewport
Yllä oletetaan, että OpenScreen-rutiinin tulos on talletettu MinunNaytto-nimiseen "muuttujaan" sekä vastaavasti oletetaan, että OpenWindow-rutiinin tulos on talletettu MinunIkkuna-nimiseen "muuttujaan". Jälleen kutsutaan WaitTOF- tai WaitBOVP-rutiinia ennen bittikarttojen funktion vaihtoa. Ja seuraavaksi tehdäänkin taas rutiini, joka asettaa toisen bittikartan näytettäväksi ja toisen piirrettäväksi:
    DoubleBuffering
                   tst.b    Kumpi
                   beq.s    zero
                   move.b   #0,Kumpi
                   move.l   viewport,a0
                   move.l   vp_RasInfo(a0),a0
                   lea      ri_BitMap(a0),a0
                   move.l   #myBitMap1,(a0)
                   move.l   rastport,a0
                   lea      rp_BitMap(a0),a0
                   move.l   #myBitMap0,(a0)
                   move.l   viewport,a0
                   LibCall  GFX,ScrollVPort
                   rts
    zero           move.b   #1,Kumpi
                   move.l   viewport,a0
                   move.l   vp_RasInfo(a0),a0
                   lea      ri_BitMap(a0),a0
                   move.l   #myBitMap0,(a0)
                   move.l   rastport,a0
                   lea      rp_BitMap(a0),a0
                   move.l   #myBitMap1,(a0)
                   move.l   viewport,a0
                   LibCall  GFX,ScrollVPort
                   rts
Jälleen ScrollVPort-rutiinia kutsutaan, jotta viewporttiin tehdyt muutokset tulevat voimaan.

Totetus erikoispiirejä suoraan käyttäen konekielellä
Tämä on siis ns. "hardiskoodausesimerkki". Aluksi on varattava muistia bittikarttoja varten. Tehdään se nyt AllocMem-rutiinia käyttäen:
                   move.l  4,a6            ; Execbase
                   move.l  #30720,d0       ; Muistin määrä tavuina
                   move.l  #65538,d1       ; MEMF_CHIP ja MEMF_CLEAR
                   jsr     -$00c6(a6)      ; AllocMem
                   move.l  d0,BitMap0      ; Muisti BitMap0:lle
Ja vastaava BitMap1:lle. Seuraavaksi tehdään rutiini, joka odottaa, että elektronisuihku on kuvaruudun alalaidassa:
    WaitForBeam    move.w  $dff004,d0      ; onko beam kuvaruudun alaosassa?
                   btst.l  #0,d0
                   beq.s   WaitForBeam
                   cmp.b   #$2c,$dff006
                   bne.s   WaitForBeam
                   rts
Enää ei oikeastaan tarvitsekaan muuta kuin vaihtaa bittikarttojen funktio ja kirjoittaa bittikarttojen tiedot copperlistaan. Tehdään se nyt:
    DoubleBuffering
                   cmp.b   #1,Kumpi
                   beq.s   YksKehiin
                   move.l  BitMap0,ShowScreen
                   move.l  BitMap1,DrawScreen
                   move.b  #1,Kumpi
                   bra.s   CopperListaan
    YksKehiin
                   move.l  BitMap1,ShowScreen
                   move.l  BitMap0,DrawScreen
                   move.b  #0,Kumpi
    CopperListaan
                   move.l  ShowScreen,d3      ; Tässä oletetaan, että
                   move.w  d3,low1            ; yhden bittitason koko
                   swap    d3                 ; on 320 * 256 pikseliä eli
                   move.w  d3,high1           ; 10240 tavua.
                   swap    d3
                   add.l   #10240,d3
                   move.w  d3,low2
                   swap    d3
                   move.w  d3,high2
                   swap    d3
                   add.l   #10240,d3
                   move.w  d3,low3
                   swap    d3
                   move.w  d3,high3
                   rts
Otetaan vielä palanen copperlistaa näkyviin:
    CopperLista
                   dc.w    $00e0             ; BPL1PTH
    high1          dc.w    $0000
                   dc.w    $00e2             ; BPL1PTL
    low1           dc.w    $0000
                   dc.w    $00e4             ; BPL2PTH
    high2          dc.w    $0000
                   dc.w    $00e6             ; BPL2PTL
    low2           dc.w    $0000
                   dc.w    $00e8             ; BPL3PTH
    high3          dc.w    $0000
                   dc.w    $00ea             ; BPL3PTL
    low3           dc.w    $0000
                   dc.w    $0100,$3200       ; BPLCON0
                   dc.w    $0102,$0000       ; BPLCON1
                   dc.w    $0108,$0000       ; BPL1MOD
                   dc.w    $010a,$0000       ; BPL2MOD
                   dc.w    $0092,$0038       ; DDFSTRT
                   dc.w    $0094,$00d0       ; DDFSTOP
                   dc.w    $008e,$2c81       ; DIWSTRT
                   dc.w    $0090,$2cc1       ; DIWSTOP
                   dc.w    $ffff,$fffe
Tässä olikin kaikki, mitä tarvitsee tuplapuskuroinnin tekemisestä tietää hardistasolla. Toki toimivan ohjelman aikaansaamiseksi tulee vielä tietää mm. miten copperlista ylipäätään laitetaan päälle, mutta se on käsitelty täydellisessä listauksessa. Tarkempi teoria bittitasojen näyttämisestä hardistasolla sivuutetaan, mutta mainittakoon DMACON-rekisteristä ($DFF096) kuitenkin, että sillä täytyy sallia DMA:n, bittitasojen DMA:n ja copperin DMA:n käyttö biteillä 9, 8 ja 7 (1. bitti on 0). Copperlistassa on kommentteina rekistereiden nimet, joita käytetään, mutta merkitys jätetään lukijan oman tutkimisen varaan; developer CD 2.1:llä on AmigaGuide-muodossa Hardware Reference Manual, josta asiaa voi tutkia tarkemmin.

Lopuksi
Toivottavasti tästä artikkelista oli hyötyä sekä toivottavasti tämä innosti ohjelmoimaan; vaikka omista hardiskoodauksistani on kulunut pitkä aika, niin tätä artikkelia tehdessäni huomasin, kuinka hauskaa se on! Ja tuplapuskurointihan on perustekniikoita esim. pelien teossa. Ohjelmoikaamme ja pitäkäämme Amiga elossa! :)

Sekalaiset Sivun alkuun Sivukartta