Palmy, které nikdo nezvětšuje - 2. díl seriálu
V minulém díle jsme postavili silnici, která se skoro nekreslí: hotové řádky, DMA a copper (nakonec se ukázalo, že existuje jednodušší řešení, které mi poradil ped7g a na kterém teď pracuji). Holá silnice ale vypadá jako zkušební polygon. Tentokrát kolem ní vysadíme palmy a borovice, postavíme cedule, lampy a billboardy. Opět skoro bez kreslení: všechno jsou hardwarové sprity, předem zmenšené. Ukážu Vám, jak takový strom schovat za horizont kopce, když se sprity ořezávat nedají, a jak do programu dostat víc grafiky, než se vejde do paměti, a to přímo z vlastního souboru NEX.
Sprity místo kreslení
Next umí zobrazit 128 hardwarových spritů velikosti 16×16 bodů. Jejich grafika, tzv. vzory (patterns), leží ve vlastní 16 KB paměti mimo adresní prostor procesoru. Při 4 bitech na bod se do ní vejde 128 vzorů po 128 bajtech. Čtyřbitový sprite si vybírá jednu z 16 palet po 16 barvách, umí se zrcadlit a každý se dá hardwarově zvětšit 2×, 4× nebo 8×, vodorovně i svisle. Strom u silnice je pak jen obdélník buněk 16×16: každá buňka je jeden sprite a na každý sprite stačí zapsat pět bajtů atributů (X, Y, paleta se zrcadlením, číslo vzoru, měřítko). Procesor tedy nekreslí žádné body. Pro celý obrázek zapíše pár bajtů, a když vzor v paměti spritů ještě není, nechá ho tam DMA přenést, 128 bajtů na buňku. Protože silnice končí na řádku 167 a pod ní je ovládací panel, dostane i vrstva spritů ořezové okno (registr $19) na řádky 0 až 167. Nastaví se jednou a dál se o spodní okraj nestaráme.
Šedesát čtyři velikostí předem
Next sprity zvětšovat umí, ale jen celočíselně. Plynulé přibližování stromu z dálky k autu je proto potřeba připravit předem. Každý objekt se nakreslí ve vysokém rozlišení, potom se plošným průměrováním zmenší do 64 velikostí a převede do 16 barev své palety. Měřítko objektu ve vzdálenosti z je s = 128 / z bodů. Velikosti tvoří geometrickou řadu od s = 0,008 do 0,56 s krokem asi 7 %. Takový krok oko při jízdě nepostřehne. Uložený obrázek smí mít nejvýš 64×64 bodů. Když je objekt větší, uloží se v polovičním nebo čtvrtinovém rozlišení a na obrazovce ho zvětší hardware 2× nebo 4×. Blízké stromy jsou proto hrubší, ale jsou blízko a míhají se rychle, takže to nevadí. Měřítko 8× už ale vypadá jako kostičky, proto se u kamery objekt zastaví na největší úrovni se čtyřnásobným zvětšením (úroveň 53).
Každý snímek (frame) popisuje v tabulce osm bajtů: stránka a první buňka grafiky, měřítko se šířkou, výška a posun levého horního rohu vůči kotvě, tedy bodu, kde se objekt dotýká země. Žádný snímek nepřekročí hranici 8 KB stránky, takže pro jeho přenos stačí namapovat jedinou stránku.
; palma, úroveň 53 db 4, 0 ; stránka (relativně k sadě), první buňka ve stránce db $83, 4 ; bity 7-6 měřítko (0 1×, 1 2×, 2 4×), bity 3-0 šířka v buňkách; výška dw -96, -256 ; levý horní roh vůči kotvě, v zobrazených bodech
Úroveň se v programu nepočítá logaritmem. Projekce už pro každý objekt zná převrácenou vzdálenost 222/z (o ní dál). Ta se posune o pět bitů doprava a výsledek je index do 1 024bajtové tabulky, která rovnou vrací číslo úrovně 0 až 63. Jeden posun a jedno čtení z paměti.
Kde objekt stojí
V minulém díle měl každý segment trati osm bajtů a dva z jeho párů byly „objekt A“ a „objekt B“. Typ objektu má v nejvyšším bitu příznak zrcadlení (jedna palma tak vypadá jako dvě různé) a stranový odstup je znaménkový bajt v násobcích 16 jednotek, záporný vlevo. Objekt stojí na začátku svého segmentu.
Projekce zná polohu na obrazovce jen pro hranice segmentů. Ty navíc v dálce pokrývají dva nebo čtyři segmenty najednou. Objekt mezi dvěma hranicemi je potřeba umístit interpolací, a tady je první past: poloha na obrazovce je lineární v 1/z, ne v z. Když se interpoluje podle světové vzdálenosti, strom se ke kameře blíží trhaně a na každé hranici poskočí, nejvíc tam, kde se mění počet segmentů na hranici. Proto se pro každý objekt spočítá jeho vlastní převrácená vzdálenost a poměr se bere z převrácených vzdáleností:
inv = 2^22 / z ; Div22: tabulka 2^30/x, 256 kroků, s interpolací t = (INV[k] - inv) / (INV[k] - INV[k+1]) ; Frac8: 0..255 y = Y[k] + (Y[k+1] - Y[k]) * t / 256 cx = CX[k] + (CX[k+1] - CX[k]) * t / 256 ; střed silnice v té vzdálenosti x = cx + odstup * inv / 2^15 ; MulS15
Dělení na Z80 je drahé, takže obojí jde přes tabulky: 222/z se normalizuje na tvar 1,xxx·215, přečte se z 256položkové tabulky převrácených hodnot s lineární interpolací a posune zpět. Poměr t se počítá násobením tabulkovou převrácenou hodnotou jmenovatele.
Objekty se zpracovávají od nejbližšího k nejvzdálenějšímu, stejně jako silnice. Sprite s nižším číslem je na Nextu nahoře (bit 6 registru $15), takže když se seznam spritů plní zepředu dozadu, bližší strom zakrývá vzdálenější bez jakéhokoli řazení. Objekty se kreslí do vzdálenosti asi 50 segmentů. Dál by z nich byly jednobodové tečky, které stojí stejně jako velký strom.
Strom za horizontem kopce
Teď to hlavní. Silnice z minulého dílu zakrývá kopcem, co je za ním, zadarmo: vzdálenější segment smí kreslit jen nad nejvyšším dosud nakresleným řádkem (clipem). Strom, který stojí za horizontem, ale musí zmizet přesně na stejné hraně, jinak by trčel ze země před kopcem. Sprite přitom ořezat nejde. Next má pro sprity jedno společné ořezové okno a každý sprite je buď vidět celý, nebo vůbec.
Rasterizace silnice si proto ke každé promítnuté hranici uloží její clip: první řádek obrazovky, který už zabírá bližší silnice. Objekt dostane clip hranice před sebou a s jeho buňkami se zachází po řadách:
- Řady buněk celé pod čarou se vůbec nevloží do seznamu spritů. Ušetří se tím i vzory.
- Řady celé nad čarou zůstanou, jak jsou.
- Řada, kterou čára protíná, dostane vlastní kopii vzorů. V ní jsou viditelné řádky bodů zkopírované z grafiky a zbytek je průhledný.
Výpočet je jednoduchý. Viditelná výška je h = clip − horní okraj. Celých řad je h >> (4 + měřítko) a řádků vzoru v protnuté řadě (h & (velikost − 1)) >> měřítko. Řádek čtyřbitového vzoru má 8 bajtů, takže se pro každou buňku protnuté řady pošle DMA n·8 bajtů z grafiky a 128 − n·8 bajtů z řádku nul, který už leží v tabulkách silnice. Přesnost je jeden řádek vzoru: bod při zvětšení 1×, dva body při 2×, čtyři při 4×. Na vzdáleném kopci, kde se ořezává nejčastěji, mají stromy měřítko 1×, takže ořez sedí na bod přesně.
Stejně se zachází se sloupci, které jsou celé mimo levý nebo pravý okraj obrazovky. Zahodí se dřív, než se pro ně začnou hledat vzory. Sprite, který z obrazovky vyčnívá jen zčásti, ořeže hardware sám, protože souřadnice X má 9 bitů.
Paměť vzorů jako cache
128 vzorů vypadá jako hodně, ale jedna blízká palma jich zabere 12, billboard 8 a každý snímek má na obrazovce desítky objektů. Vzory se proto nenahrávají znovu každý snímek, ale používají se jako cache. Každý vzor má klíč: sada, stránka a první buňka grafiky, počet buněk a tvar ořezu. Když se objekt v dalším snímku nezmění velikost (a to je většina, protože úrovně jsou po 7 %), jeho vzory už v paměti jsou a stačí zapsat atributy.
Hledání nesmí stát víc, než kolik ušetří. Prohledávat 128 klíčů pro každý objekt by bylo drahé, takže klíč projde jednoduchým hashem do tabulky nápověd se dvěma cestami: obrázek, který s tímto hashem přišel naposledy, a ten před ním. Když v žádné z cest není, obrázek se prostě nahraje znovu. Kopie, která zůstala jinde, časem vyprchá.
Když nepomůže ani odložení, objekt se nezahodí hned. Nejdřív se zkusí o úroveň menší snímek, který často v paměti ještě je, protože byl vidět v minulém snímku. Teprve když se nevejde nic, objekt v tomto snímku chybí. Na oko je to malé zaváhání ve velikosti, ne blikání.
Ve hře, kde se kolem jezdí ještě se sedmi soupeři, vychází v průměru asi 57 kreslených obrázků na snímek a z toho jen kolem 21 potřebuje nahrát vzory. Jeden nahraný vzor je 128 bajtů DMA, tedy zhruba 1 300 taktů, necelý rastrový řádek.
Víc grafiky, než se vejde
Next s 2 MB má 224 stránek po 8 KB. Předpočítané řádky silnice z minulého dílu zabírají 62 stránek, sprity aut desítky dalších. Hra má čtyři prostředí: pobřeží s palmami, hory se zasněženými smrky a chatami, poušť s kaktusy a červenými skalami, les s duby a balíky slámy. Objekty, které má jen jedno prostředí, by spolu zabraly 52 stránek, a přitom se v jednom závodě uplatní vždy jen jedno prostředí.
Řešení je nedržet je v paměti vůbec a dočíst je z disku těsně před závodem. Nemusí přitom ležet v samostatném souboru, který by se mohl ztratit. Mohou být přímo na konci souboru NEX.
sjasmplus na to má dvě věci. SAVENEX CLOSE "soubor" připojí za banky libovolný blok dat. SAVENEX CFG má druhý parametr, adresu, kam loader zapíše číslo otevřeného souboru: loader pak soubor po nahrání bank nezavře a nechá ho otevřený přesně za poslední bankou, tedy na začátku našich dat.
SAVENEX OPEN "tutorial2.nex", Start, STACK_TOP
SAVENEX CORE 3,0,0
SAVENEX CFG 0,NexHandle,0,1 ; soubor zůstane otevřený
SAVENEX AUTO
SAVENEX CLOSE "assets/tutorial/objects.bin"
Pak už se jen čte. Tutoriál všechno načte hned po startu, stránku po stránce, a soubor zavře:
LoadScenery:
ld a, (NexHandle)
inc a
jr z, .none ; nespuštěno jako NEX: bez palem
nextreg NR_MMU0, 255 ; ROM zpět na $0000 kvůli RST $08
nextreg NR_MMU1, 255
ld iy, $5C3A
ld bc, (TUT_OBJ_PAGES+1)*256 + OBJ_TAB_PAGE
.p: push bc
ld a, c
nextreg NR_MMU7, a ; další stránka na $E000
ld a, (NexHandle)
ld ix, $E000 ; adresa v IX, ne v HL!
ld bc, 8192
rst $08
db $9D ; F_READ
pop bc
jr c, .fail
inc c
djnz .p
… ; F_CLOSE, tabulky snímků na místo
Hra dělá totéž chytřeji. Hned po startu si zjistí aktuální pozici v souboru (F_SEEK o nulu od aktuální pozice vrátí pozici), tedy začátek připojených dat, a soubor nechá otevřený. Před závodem v jiném prostředí pak skočí na příslušnou stránku a načte jen to, co potřebuje: jednu stránku tabulek a pět až šestnáct stránek grafiky. Na SD kartě to trvá zlomek sekundy.
Aby to fungovalo, musí být grafika přemístitelná. Tabulka snímků proto čísluje stránky relativně k první stránce sady. Sady všech prostředí se načítají do stejné oblasti paměti a program jen ví, že palma začíná na začátku oblasti a chata o sedm stránek dál. Tabulky snímků mají vyhrazené místo v trvale namapované stránce tabulek a přepíší se při každé změně prostředí.
-mmc=.\, jinak RST $08 nemá kam sáhnout.Když se data načíst nepodaří (třeba emulátor neumí nechat soubor otevřený), tutoriál nespadne. Palmy jen vyřadí z tabulky typů objektů a jede se bez nich. Borovice, keře, kameny, cedule a lampy jsou v bankách NEXu a uvidíte je vždycky.
Shrnutí jednoho objektu
Cesta jedné palmy od dat trati na obrazovku:
- Segment trati má v záznamu typ, zrcadlení a odstup.
- Z polohy segmentu se spočítá 222/z a z ní úroveň velikosti (tabulka) a poloha mezi dvěma promítnutými hranicemi (interpolace v 1/z).
- Clip hranice před objektem určí, kolik řad buněk zůstane a kterou řadu je potřeba ořezat.
- Cache vzorů najde obrázek v paměti spritů, nebo ho nahraje, hned nebo v zatemnění.
- Do seznamu spritů se zapíše pět bajtů za každou viditelnou buňku. Seznam se pošle do Nextu DMA v zatemnění spolu s copperem.
Kreslící smyčka je celá v jedné rutině DrawFrame a její vnitřní cyklus přes buňky si konstanty obrázku (měřítko, paletu, krok X) zapíše přímo do vlastních instrukcí, aby na buňku zbylo jen pár registrových operací.
tutorial2.nex je stejný program jako v minulém díle, jen s objekty kolem první tratě: palmami, borovicemi, keři, kameny, cedulemi do zatáček, lampami, billboardem a startovní bránou. Palmy se načítají z konce souboru NEX. Auto zatím projíždí objekty skrz, srážky jsou látka na jindy. A nebo si ho jednoduše spusťte v našem online emulátoru přímo zde:
Komentáře
Zatím tu nikdo nic nenapsal. Buď první!