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.

alkuun
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.


alkuun
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.

alkuun
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.

alkuun
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.

alkuun
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