Obsah
1. Barvy použité při zobrazení spritů
2. Uložení indexu barvové palety v atributech spritů
3. Realizace změny barvové palety spritů při stisku tlačítka A
4. Úplný zdrojový kód prvního demonstračního příkladu
5. Zpomalení změny barvové palety spritů při stisku tlačítka
6. Realizace jednoduchého čítače
7. Úplný zdrojový kód druhého demonstračního příkladu
8. Horizontální a vertikální zrcadlení spritů
9. Makro pro inverzi vybraného bitu či bitů v paměťovém bloku
10. Realizace zrcadlení spritů řízených hráčem
11. Úplný zdrojový kód třetího demonstračního příkladu
12. Celková velikost vygenerovaného strojového kódu
13. Repositář s demonstračními příklady
1. Barvy použité při zobrazení spritů
V první polovině dnešního článku si ukážeme, jakým způsobem je možné modifikovat barvy spritů zobrazených na displeji řízeného osmibitovou herní konzolí NES. Jedná se o relativně komplikovanou techniku, protože barvy všech objektů ve scéně nejsou určeny přímo (například s využitím RGB), ale je zde zvolen odlišný způsob – mapování barev s využitím barvové palety (color table). Ovšem ve skutečnosti je situace ještě nepatrně složitější, protože se nepracuje přímo s indexy do jedné barvové palety, ale je prováděno dvojí mapování, což je sice z pohledu vývojáře složitější řešení, ale počet operací s pamětí se poměrně radikálním způsobem snižuje, stejně jako celkové nároky na kapacitu RAM i ROM (ROM je přitom instalována na cartridgi, jejíž celková cena se nepřímo promítá do ceny každé hry).
Připomeňme si, že barvová paleta používaná v daném okamžiku je uložena v operační paměti od adresy $3f00, tedy konkrétně na konci třetí stránky paměti (každá stránka má 256 bajtů):
PALETTE = $3f00
Celková délka palety je rovna 32 bajtům, přičemž nejhrubší rozdělení je na šestnáct barev pozadí (background) a šestnáct barev spritů:
; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů
Hodnoty zde uložené jsou indexy do této barvové škály:
Obrázek 1: Barvová paleta používaná herní konzolí NES.
Ve skutečnosti je oněch 32 bajtů rozděleno nikoli do pouhých dvou oblastí, ale do devíti bloků:
Od | Do | Význam |
---|---|---|
0×3f00 | (jediný bajt) | globální barva pozadí |
0×3f01 | 0×3f03 | paleta pozadí #0 |
0×3f05 | 0×3f07 | paleta pozadí #1 |
0×3f09 | 0×3f0b | paleta pozadí #2 |
0×3f0d | 0×3f0f | paleta pozadí #3 |
0×3f11 | 0×3f13 | paleta spritů #0 |
0×3f15 | 0×3f17 | paleta spritů #1 |
0×3f19 | 0×3f1b | paleta spritů #2 |
0×3f1d | 0×3f1f | paleta spritů #3 |
Zaměřme se nyní na barvy pixelů v zobrazených spritech. Skutečná barva je vybrána z výše uvedené palety (32 kódů barev), ovšem index do této palety se počítá složitějším způsobem. Konkrétní paleta #0 až #3 je uložena v atributu spritu, konkrétně v dolních dvou bitech atributového bajtu (viz další kapitolu) – to znamená, že můžeme barvovou paletu snadno modifikovat zápisem jediného bajtu do RAM. A barva v rámci této palety je získána ze dvou bitů bitmapy, která popisuje vlastní tvar spritu. Přitom platí, že barva číslo 0 je průhledná, takže sprite v pixelech s touto barvou není vykreslen a prosvítá zde buď jiný sprite nebo pozadí.
Obrázek 2: V tomto editoru spritů je patrné, jak jsou bitmapy spritů uloženy v ROM. Každý pixel může nabývat jedné ze čtyř barev (první barva je přitom při zobrazení na displeji průhledná) a jedná se o nepravé barvy. Konkrétní barva spritu je získána až výběrem určité palety čtyř barev atributovým bajtem (resp. dvěma bity atributového bajtu).
2. Uložení indexu barvové palety v atributech spritů
Ještě jednou se podívejme na to, jaké metainformace o spritech jsou uloženy v operační paměti dostupné mikroprocesoru MOS 6502. Připomeňme si, že pro uložení těchto informací máme rezervovanou celou druhou stránku operační paměti, tj. paměťové buňky s adresami $0200 až $02ff. Celkem je možné do těchto 256 bajtů uložit metainformace o 64 spritech, protože pro každý sprite jsou vyhrazeny čtyři bajty. Nás nyní bude nejvíce zajímat třetí bajt s atributy spritů. Prozatím zobrazujeme jen osm spritů, takže se celkově bude jednat o 8×4=32 bajtů:
; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10
Třetí bajt z celé čtyřbajtové struktury s metainformacemi o spritu obsahuje jedno bitové pole a tři samostatné bity, které řídí způsob zobrazení daného spritu (a to zcela nezávisle na ostatních spritech):
7 6 5 4 3 2 1 0 | | | | | | | | | | | | | | +-+- Index barvové palety | | | | | | | | | +-+-+----- Nepoužito | | | | | +----------- Priorita (0: před pozadím; 1: za pozadím) | | | +------------- Horizontální zrcadlení spritu | +--------------- Vertikální zrcadlení spritu
Prozatím nás budou zajímat nejnižší dva bity, které určují index do barvové palety – viz též úvodní kapitolu s podrobnějšími informacemi.
Obrázek 3: Sprity zobrazené s využitím první barvové palety.
Obrázek 4: Sprity zobrazené s využitím druhé barvové palety.
Obrázek 5: Sprity zobrazené s využitím třetí barvové palety.
Obrázek 6: Sprity zobrazené s využitím čtvrté barvové palety.
3. Realizace změny barvové palety spritů při stisku tlačítka A
V této kapitole si ukážeme, jakým způsobem lze realizovat změnu barvové palety spritů zobrazených na obrazovce (konkrétně se jedná o osm spritů tvořících figurku Maria), a to konkrétně (opakovaným) stiskem tlačítka A na prvním herním ovladači.
Nejprve je nutné načíst stav všech osmi tlačítek do záchytného registru. To je technika, s níž jsme se již seznámili minule a spočívá v poslání signálu latch (záchyt hodnot) do řídicího registru $4016, jenž je v našich zdrojových kódech pojmenován JOYPAD:
lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right
Po provedení této operace je nutné osmkrát přečíst obsah řídicího registru JOYPAD1 a získat tak postupně stav všech osmi tlačítek ovladače. Nás dnes bude zajímat jen přečtení stavu tlačítka A. V případě, že je toto tlačítko stlačeno, provede se kód zapsaný v makru increment_block_mask:
ATTRS = $0202 ; adresa buňky paměti s atributy spritu read_button ; stisk tlačítka A bude sloužit pro přepínání barvy spritů beq button_a_not_pressed ; není stisknuto? => skok increment_block_mask ATTRS, 8, 4, 3 button_a_not_pressed: ... ... ...
Makro increment_block_mask zvýší obsah buněk ve vybraném paměťovém bloku, přičemž se po zvýšení hodnoty dané paměťové buňky provede maskování hodnoty bitovou maskou uloženou v parametru mask (my použijeme hodnotu 3, což znamená, že se nastaví jen dva nejnižší bity). Jak již dobře víme z minulého článku, nebudeme pracovat s blokem paměťových buněk uložených ihned za sebou, ale naopak s buňkami, mezi nimiž se nachází tři další bajty, které měnit nechceme. Adresa další paměťové buňky je tedy zvyšována nikoli o jedničku, ale o hodnotu specifikovanou v parametru gap (což je konkrétně hodnota 4):
.macro increment_block_mask address, count, gap, mask ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku lda address, x ; maskování hodnoty and #mask sta address, x txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro
4. Úplný zdrojový kód prvního demonstračního příkladu
Úplný zdrojový kód dnešního prvního demonstračního příkladu (v pořadí již dvacátého druhého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example22.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example21.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. ; Pohyb celého Maria. ; Využití symbolických jmen adres. ; Pomocná makra pro pohyb spritu. ; Změna dalších vlastností spritů s využitím tlačítek A a B ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro increment_block address, count, gap ldx #0 ; inicializace okonkrétní ffsetu : inc address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro decrement_block address, count, gap ldx #0 ; inicializace offsetu : dec address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro increment_block_mask address, count, gap, mask ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku lda address, x ; maskování hodnoty and #mask sta address, x txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu ATTRS = $0202 ; adresa buňky paměti s atributy spritu read_button ; stisk tlačítka A bude sloužit pro přepínání barvy spritů beq button_a_not_pressed ; není stisknuto? => skok increment_block_mask ATTRS, 8, 4, 3 button_a_not_pressed: read_button ; stav tlačítka B jen načteme a ingorujeme read_button ; stav tlačítka Select jen načteme a ingorujeme read_button ; stav tlačítka Start jen načteme a ingorujeme read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok decrement_block YPOS, 8, 4 up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok increment_block YPOS, 8, 4 down_not_pressed: read_button ; stav tlačítka Left beq left_not_pressed ; není stisknuto? => skok decrement_block XPOS, 8, 4 left_not_pressed: read_button ; stav tlačítka Right beq right_not_pressed ; není stisknuto? => skok increment_block XPOS, 8, 4 right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
5. Zpomalení změny barvové palety spritů při stisku tlačítka
Pokud jste si předchozí demonstrační příklad přeložili a spustili, pravděpodobně jste již přišli na jednu jeho nepříjemnou vlastnost – změna barvové palety spritů totiž probíhá (pokud je pochopitelně tlačítko A stisknuto) velmi rychle. Je tomu tak z toho jednoduchého důvodu, že se test stisku tlačítka a případná změna palety provádí v subrutině VBLANK volané automaticky 50× až 60× za sekundu. To znamená, že i změna vybrané barvové palety probíhá s touto relativně vysokou frekvencí. Toto chování nijak nevadilo ve chvíli, kdy jsme zajišťovali pohyb spritu, protože změna pozice spritu 50×/60× za sekundu znamená, že sprite přejede přes celou obrazovku za přibližně pět sekundu (256/50 resp. 256/60 – lze si snadno a dokonce i relativně přesně ověřit stopkami). Nicméně se vraťme k problematice změny barvové palety. Bylo by ideální, kdyby tato změna probíhala s menší frekvencí, řekněme jen několikrát za sekundu. Toho lze relativně snadno dosáhnout s využitím čítače, který je postupně (s frekvencí 50 resp. 60 Hz) snižován na nulu a teprve při dosažení nuly se provede příslušná změna atributu spritů (a hodnota čítače je obnovena na původní hodnotu).
To znamená, že frekvence změny barvové palety bude přibližně rovna:
50/počáteční_hodnota_čítače
nebo:
60/počáteční_hodnota_čítače
6. Realizace jednoduchého čítače
Nyní se podívejme na způsob realizace čítače zmíněného v předchozí kapitole. Hodnota čítače bude maximálně osmibitová a čítač uložíme do nulté stránky paměti, z níž je možné data číst či zapisovat efektivněji – všechny instrukce pracující s nultou stránkou paměti jsou totiž kratší o jeden bajt a taktéž rychlejší (typicky o jeden strojový cyklus – viz například https://www.masswerk.at/6502/6502_instruction_set.html#LDA):
addressing assembler opc bytes cycles immediate LDA #oper A9 2 2 zeropage LDA oper A5 2 3 zeropage,X LDA oper,X B5 2 4 absolute LDA oper AD 3 4 absolute,X LDA oper,X BD 3 4* absolute,Y LDA oper,Y B9 3 4* (indirect,X) LDA (oper,X) A1 2 6 (indirect),Y LDA (oper),Y B1 2 5*
Definujme tedy adresu, na níž je čítač uložen (poslední bajt nulté stránky):
; Čítač COUNTER = $00ff
Čítač nastavíme na výchozí hodnotu v rutině RESET, která je zavolána automaticky při inicializaci herní konzole:
; Obslužná rutina pro RESET .proc reset ... ... ... lda #10 ; inicializace čítače sta COUNTER ... ... ... game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc
A v obslužné rutině VBLANK provedeme tento pseudokód:
if stisknuto(tlačítko_a) { čítač -= 1 if čítač == 0 { čítač = výchozí_hodnota změn_atributy(sprite0..7) } }
Výše uvedených sedm řádků pseudokódu lze zapsat do pouhých osmi řádků v assembleru:
read_button ; stisk tlačítka A bude sloužit pro přepínání barvy spritů beq button_a_not_pressed ; není stisknuto? => skok dec COUNTER ; snížení hodnoty čítače a test na nulu bne button_a_not_pressed ; čítač != 0? => skok lda #10 ; nastavení výchozí hodnoty čítače sta COUNTER increment_block_mask ATTRS, 8, 4, 3 button_a_not_pressed:
7. Úplný zdrojový kód druhého demonstračního příkladu
Úplný zdrojový kód dnešního druhého demonstračního příkladu (v pořadí již dvacátého třetího příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example23.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example23.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. ; Pohyb celého Maria. ; Využití symbolických jmen adres. ; Pomocná makra pro pohyb spritu. ; Změna dalších vlastností spritů s využitím tlačítek A a B ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; Čítač COUNTER = $00ff ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro increment_block address, count, gap ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro decrement_block address, count, gap ldx #0 ; inicializace offsetu : dec address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro increment_block_mask address, count, gap, mask ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku lda address, x ; maskování hodnoty and #mask sta address, x txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu ATTRS = $0202 ; adresa buňky paměti s atributy spritu read_button ; stisk tlačítka A bude sloužit pro přepínání barvy spritů beq button_a_not_pressed ; není stisknuto? => skok dec COUNTER bne button_a_not_pressed lda #10 sta COUNTER increment_block_mask ATTRS, 8, 4, 3 button_a_not_pressed: read_button ; stav tlačítka B jen načteme a ingorujeme read_button ; stav tlačítka Select jen načteme a ingorujeme read_button ; stav tlačítka Start jen načteme a ingorujeme read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok decrement_block YPOS, 8, 4 up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok increment_block YPOS, 8, 4 down_not_pressed: read_button ; stav tlačítka Left beq left_not_pressed ; není stisknuto? => skok decrement_block XPOS, 8, 4 left_not_pressed: read_button ; stav tlačítka Right beq right_not_pressed ; není stisknuto? => skok increment_block XPOS, 8, 4 right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny lda #10 ; inicializace čítače sta COUNTER ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
8. Horizontální a vertikální zrcadlení spritů
Vraťme se nyní k obsahu bajtu, který pro každý sprite zvlášť určuje, jakým způsobem se má sprite zobrazit. Již víme, jakou roli hrají dva nejnižší bity – určují index barvové palety. Další tři bity jsou nevyužity a nejvyšší tři bity určují prioritu (ukážeme si příště), horizontální zrcadlení spritu a vertikální zrcadlení spritu:
7 6 5 4 3 2 1 0 | | | | | | | | | | | | | | +-+- Index barvové palety | | | | | | | | | +-+-+----- Nepoužito | | | | | +----------- Priorita (0: před pozadím; 1: za pozadím) | | | +------------- Horizontální zrcadlení spritu | +--------------- Vertikální zrcadlení spritu
Díky možnosti individuálního zrcadlení spritů je možné, aby se Mario (či další postavy ve hrách) pohybovaly doprava i doleva, a to bez nutnosti mít pro každý směr rezervovány další sprity (tedy „obličej doprava“ i „obličej doleva“). Zrcadlení je možné využít i k dalším trikům, k nimž se vrátíme později. V každém případě se jedná o nenápadnou, ale o to důležitější součást grafického subsystému NESu.
Obrázek 7: Nezrcadlené sprity.
Obrázek 8: Vertikální zrcadlení.
Obrázek 9: Horizontální zrcadlení.
Obrázek 10: Horizontální i vertikální zrcadlení.
9. Makro pro inverzi vybraného bitu či bitů v paměťovém bloku
Pokud budeme chtít zrcadlit (ať již vertikálně či horizontálně) všech osm spritů tvořících postavičku Maria, bude nutné projít všemi osmi atributovými bajty a nastavit nebo invertovat buď sedmý bit nebo bit šestý. K tomuto účelu lze použít instrukci nazvanou EOR neboli exclusive or (známá na jiných platformách jako XOR). Inverzi sedmého (nejvyššího) bitu tak můžeme provést takto:
lda address, x ; maskování hodnoty eor #%10000000 sta address, x
podobně inverze šestého bitu se provede následovně:
lda address, x ; maskování hodnoty eor #%01000000 sta address, x
přičemž v address je uložena počáteční adresa metainformací o spritech (tedy konkrétně hodnota $0200) a v registru x offset atributového bajtu.
Můžeme tedy velmi snadno upravit již existující makro increment_block_mask tak, aby se namísto pouhého zvýšení obsahu atributového bajtu (s následným maskováním) jen invertoval jediný bit tohoto atributu. Maska pro inverzi je předána v parametru mask:
.macro flip_bit_block address, count, gap, mask ldx #0 ; inicializace offsetu : lda address, x ; maskování hodnoty eor #mask sta address, x txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro
Příklad aplikace tohoto makra pro inverzi šestého bitu:
flip_bit_block ATTRS, 8, 4, %01000000
Pro úplnost dodejme seznam parametrů makra:
Parametr | Význam |
---|---|
ATTRS | adresa buňky paměti s atributy prvního spritu ($0202) |
8 | celkový počet spritů, jejichž atributy se mají měnit |
4 | offset mezi dvěma sousedními atributovými bajty |
%01000000 | vlastní bitová maska (zapsána binárně) |
10. Realizace zrcadlení spritů řízených hráčem
Vlastní realizace zrcadlení spritů bude jednoduchá. Zrcadlení jsou realizována stiskem tlačítek B a Select. Stisk každého z těchto tlačítek vede k zahájení odpočítávání čítače a při dosažení nuly se neguje nejvyšší bit nebo šestý bit atributového bajtu všech prvních osmi spritů:
read_button ; stisk tlačítka B bude sloužit pro přepínání atributů spritů beq button_b_not_pressed ; není stisknuto? => skok dec COUNTER2 bne button_b_not_pressed lda #10 sta COUNTER2 flip_bit_block ATTRS, 8, 4, %01000000 button_b_not_pressed: read_button ; stisk tlačítka Select bude sloužit pro přepínání atributů spritů beq button_select_not_pressed ; není stisknuto? => skok dec COUNTER2 bne button_select_not_pressed lda #10 sta COUNTER2 flip_bit_block ATTRS, 8, 4, %10000000 button_select_not_pressed:
11. Úplný zdrojový kód třetího demonstračního příkladu
Úplný zdrojový kód dnešního třetího demonstračního příkladu (v pořadí již dvacátého čtvrtého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example24.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example24.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. ; Pohyb celého Maria. ; Využití symbolických jmen adres. ; Pomocná makra pro pohyb spritu. ; Změna dalších vlastností spritů s využitím tlačítek A a B ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; Čítače COUNTER1 = $00fe COUNTER2 = $00ff ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro increment_block address, count, gap ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro decrement_block address, count, gap ldx #0 ; inicializace offsetu : dec address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro increment_block_mask address, count, gap, mask ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku lda address, x ; maskování hodnoty and #mask sta address, x txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro flip_bit_block address, count, gap, mask ldx #0 ; inicializace offsetu : lda address, x ; maskování hodnoty eor #mask sta address, x txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu ATTRS = $0202 ; adresa buňky paměti s atributy spritu read_button ; stisk tlačítka A bude sloužit pro přepínání barvy spritů beq button_a_not_pressed ; není stisknuto? => skok dec COUNTER1 bne button_a_not_pressed lda #10 sta COUNTER1 increment_block_mask ATTRS, 8, 4, 3 button_a_not_pressed: read_button ; stisk tlačítka B bude sloužit pro přepínání atributů spritů beq button_b_not_pressed ; není stisknuto? => skok dec COUNTER2 bne button_b_not_pressed lda #10 sta COUNTER2 flip_bit_block ATTRS, 8, 4, %01000000 button_b_not_pressed: read_button ; stisk tlačítka Select bude sloužit pro přepínání atributů spritů beq button_select_not_pressed ; není stisknuto? => skok dec COUNTER2 bne button_select_not_pressed lda #10 sta COUNTER2 flip_bit_block ATTRS, 8, 4, %10000000 button_select_not_pressed: read_button ; stav tlačítka Start jen načteme a ingorujeme read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok decrement_block YPOS, 8, 4 up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok increment_block YPOS, 8, 4 down_not_pressed: read_button ; stav tlačítka Left beq left_not_pressed ; není stisknuto? => skok decrement_block XPOS, 8, 4 left_not_pressed: read_button ; stav tlačítka Right beq right_not_pressed ; není stisknuto? => skok increment_block XPOS, 8, 4 right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny lda #10 ; inicializace čítačů sta COUNTER1 sta COUNTER2 ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
12. Celková velikost vygenerovaného strojového kódu
Zdrojový kód demonstračních příkladů již překročil 400 řádků, takže (pokud by se jednalo o vyšší programovací jazyk) by bylo vhodné zjistit, kolik ROM jsme vlastně prozatím obsadili (v případě RAM to víme – čtyři paměťové stránky, takže 1kB, z toho naprostá většina místa je prozatím nevyužita). ROM je rozdělena do jednotlivých segmentů, přičemž velikosti segmentů je možné v případě assembleru ca65 zjistit tak, že se při překladu dvakrát použije přepínač -v -v (druhou možností je vyčtení stejných údajů z listingu):
$ ca65 example24.asm -o example24.o -v -v
Assembler v tomto případě vypíše obsah jednotlivých buněk ROM, což nás ovšem nyní nezajímá. Důležitější jsou pro nás informace na řádcích End PC, které udávají adresu poslední zapisované buňky a tedy i velikost segmentu:
New segment: CODE Literal: A9 02 8D 14 40 A9 01 8D 16 40 A9 00 8D 16 40 AD 16 40 Literal: 29 01 F0 1E C6 FE D0 1A A9 0A 85 FE A2 00 FE 02 02 ... ... ... Literal: FB A9 00 95 00 9D 00 01 9D 00 02 9D 00 03 9D 00 04 Literal: 9D 00 05 9D 00 06 9D 00 07 E8 D0 E6 2C 02 20 10 FB Literal: 20 Expression (2): SYM( SEC $013A +) ... ... ... Literal: 17 0F 22 16 27 18 22 1A 30 27 22 16 30 27 22 0F 36 Literal: 17 10 00 00 08 10 01 00 10 18 02 00 08 18 03 00 10 Literal: 20 04 00 08 20 05 00 10 28 06 00 08 28 07 00 10 End PC = $01AE New segment: HEADER Literal: 4E 45 53 1A 02 01 01 00 End PC = $0008 New segment: VECTORS Expression (2): SYM( SEC) Expression (2): SYM( SEC $00D3 +) Expression (2): SYM( SEC $00D2 +) End PC = $0006 New segment: CHARS Literal: 03 0F 1F 1F 1C 24 26 66 00 00 00 00 1F 3F 3F 7F E0 C0 80 FC 80 C0 00 20 Literal: 00 20 60 00 F0 FC FE FE 60 70 18 07 0F 1F 3F 7F 7F 7F 1F 07 00 1E 3F 7F Literal: FC 7C 00 00 E0 F0 F8 F8 FC FC F8 C0 C2 67 2F 37 7F 7F FF FF 07 07 0F 0F ... ... ... Literal: 00 58 52 46 58 52 C6 9C F7 FF C6 F6 FE C6 F6 7A 00 7B 42 02 7A 42 02 7A Literal: 38 BC F6 F6 F6 F6 FE 5C 00 18 52 52 52 52 46 5C 00 FF FF FF FF FF FF FF Literal: FF FF FF FF FF FF FF FF End PC = $2000
Shrňme si tedy zjištěné informace do tabulky:
Segment | Velikost (hex) | Velikost (dec) | Poznámka |
---|---|---|---|
CODE | $01AE | 430 | veškerý náš programový kód po překladu |
HEADER | $0008 | 8 | hlavička ROM má pevně zadanou strukturu i délku 8 bajtů |
VECTORS | $0006 | 6 | tři vektory pro RESET, IRQ a NMI: 3×2=6 bajtů |
CHARS | $2000 | 8192 | dlaždice 8×8 pixelů s 2bpp (16 bajtů). 256 dlaždic pozadí + 256 dlaždic spritů |
Strojový kód nám tedy poněkud narostl, a to mj. i kvůli barvové paletě (32 bajtů+obslužná rutina), datům spritů (32 bajtů) a expanzi maker. Nicméně s dalším rozšiřováním programové logiky bude velikost kódu narůstat velmi pomalu – a necelých 0,5kB je stále dobrá hodnota. V případě potřeby často volaná makra předěláme na běžné podprogramy (subrutiny).
13. Repositář s demonstračními příklady
Demonstrační příklady napsané v assembleru, které jsou určené pro překlad pomocí assembleru ca65 (jenž je součástí cc65), byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
14. Odkazy na Internetu
- The Thirty Million Line Problem
https://www.youtube.com/watch?v=kZRE7HIO3vk - NesDev.org
https://www.nesdev.org/ - The Sprite Attribute Byte
https://www.patater.com/nes-asm-tutorials/day-17/ - How to Program an NES game in C
https://nesdoug.com/ - Cycle reference chart
https://www.nesdev.org/wiki/Cycle_reference_chart - Getting Started Programming in C: Coding a Retro Game with C Part 2
https://retrogamecoders.com/getting-started-with-c-cc65/ - NES game development in 6502 assembly – Part 1
https://kibrit.tech/en/blog/nes-game-development-part-1 - NES (Nintendo Entertainment System) controller pinout
https://pinoutguide.com/Game/NES_controller_pinout.shtml - NES Controller Shift Register
https://www.allaboutcircuits.com/uploads/articles/nes-controller-arduino.png?v=1469416980041 - „Game Development in Eight Bits“ by Kevin Zurawel
https://www.youtube.com/watch?v=TPbroUDHG0s&list=PLcGKfGEEONaBjSfQaSiU9yQsjPxxDQyV8&index=4 - Game Development for the 8-bit NES: A class by Bob Rost
http://bobrost.com/nes/ - Game Development for the 8-bit NES: Lecture Notes
http://bobrost.com/nes/lectures.php - NES Graphics Explained
https://www.youtube.com/watch?v=7Co_8dC2zb8 - NES GAME PROGRAMMING PART 1
https://rpgmaker.net/tutorials/227/?post=240020 - NES 6502 Programming Tutorial – Part 1: Getting Started
https://dev.xenforo.relay.cool/index.php?threads/nes-6502-programming-tutorial-part-1-getting-started.858389/ - Minimal NES example using ca65
https://github.com/bbbradsmith/NES-ca65-example - List of 6502-based Computers and Consoles
https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/ - History of video game consoles (second generation): Wikipedia
http://en.wikipedia.org/wiki/History_of_video_game_consoles_(second_generation) - 6502 – the first RISC µP
http://ericclever.com/6500/ - 3 Generations of Game Machine Architecture
http://www.atariarchives.org/dev/CGEXPO99.html - bee – The Multi-Console Emulator
http://www.thebeehive.ws/ - Nerdy Nights Mirror
https://nerdy-nights.nes.science/ - The Nerdy Nights ca65 Remix
https://github.com/ddribin/nerdy-nights - NES Development Day 1: Creating a ROM
https://www.moria.us/blog/2018/03/nes-development - How to Start Making NES Games
https://www.matthughson.com/2021/11/17/how-to-start-making-nes-games/ - ca65 Users Guide
https://cc65.github.io/doc/ca65.html - cc65 Users Guide
https://cc65.github.io/doc/cc65.html - ld65 Users Guide
https://cc65.github.io/doc/ld65.html - da65 Users Guide
https://cc65.github.io/doc/da65.html - Nocash NES Specs
http://nocash.emubase.de/everynes.htm - Nintendo Entertainment System
http://cs.wikipedia.org/wiki/NES - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - NesDev
http://nesdev.parodius.com/ - 2A03 technical reference
http://nesdev.parodius.com/2A03%20technical%20reference.txt - NES Dev wiki: 2A03
http://wiki.nesdev.com/w/index.php/2A03 - Ricoh 2A03
http://en.wikipedia.org/wiki/Ricoh_2A03 - 2A03 pinouts
http://nesdev.parodius.com/2A03_pinout.txt - 27c3: Reverse Engineering the MOS 6502 CPU (en)
https://www.youtube.com/watch?v=fWqBmmPQP40 - “Hello, world” from scratch on a 6502 — Part 1
https://www.youtube.com/watch?v=LnzuMJLZRdU - A Tour of 6502 Cross-Assemblers
https://bumbershootsoft.wordpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/ - Nintendo Entertainment System (NES)
https://8bitworkshop.com/docs/platforms/nes/ - Question about NES vectors and PPU
https://archive.nes.science/nesdev-forums/f10/t4154.xhtml - How do mapper chips actually work?
https://archive.nes.science/nesdev-forums/f9/t13125.xhtml - INES
https://www.nesdev.org/wiki/INES - NES Basics and Our First Game
http://thevirtualmountain.com/nes/2017/03/08/nes-basics-and-our-first-game.html - Where is the reset vector in a .nes file?
https://archive.nes.science/nesdev-forums/f10/t17413.xhtml - CPU memory map
https://www.nesdev.org/wiki/CPU_memory_map - How to make NES music
http://blog.snugsound.com/2008/08/how-to-make-nes-music.html - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - MIDINES
http://www.wayfar.net/0×f00000_overview.php - FamiTracker
http://famitracker.com/ - nerdTracker II
http://nesdev.parodius.com/nt2/ - How NES Graphics work
http://nesdev.parodius.com/nesgfx.txt - NES Technical/Emulation/Development FAQ
http://nesdev.parodius.com/NESTechFAQ.htm - Adventures with ca65
https://atariage.com/forums/topic/312451-adventures-with-ca65/ - example ca65 startup code
https://atariage.com/forums/topic/209776-example-ca65-startup-code/ - 6502 PRIMER: Building your own 6502 computer
http://wilsonminesco.com/6502primer/ - 6502 Instruction Set
https://www.masswerk.at/6502/6502_instruction_set.html - Chip Hall of Fame: MOS Technology 6502 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor - Single-board computer
https://en.wikipedia.org/wiki/Single-board_computer - www.6502.org
http://www.6502.org/ - 6502 PRIMER: Building your own 6502 computer – clock generator
http://wilsonminesco.com/6502primer/ClkGen.html - Great Microprocessors of the Past and Present (V 13.4.0)
http://www.cpushack.com/CPU/cpu.html - Jak se zrodil procesor?
https://www.root.cz/clanky/jak-se-zrodil-procesor/ - Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/ - Mikrořadiče a jejich použití v jednoduchých mikropočítačích
https://www.root.cz/clanky/mikroradice-a-jejich-pouziti-v-jednoduchych-mikropocitacich/ - Mikrořadiče a jejich aplikace v jednoduchých mikropočítačích (2)
https://www.root.cz/clanky/mikroradice-a-jejich-aplikace-v-jednoduchych-mikropocitacich-2/ - 25 Microchips That Shook the World
https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world - Comparison of instruction set architectures
https://en.wikipedia.org/wiki/Comparison_of_instruction_set_architectures - Day 1 – Beginning NES Assembly
https://www.patater.com/nes-asm-tutorials/day-1/ - Day 2 – A Source Code File's Structure
https://www.patater.com/nes-asm-tutorials/day-2/ - Assembly Language Misconceptions
https://www.youtube.com/watch?v=8_0tbkbSGRE - How Machine Language Works
https://www.youtube.com/watch?v=HWpi9n2H3kE