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:
paci sa mi velka draw distance, nielen jedna zakruta a jeden kopec, sprajtov je vela a su "na husto", takze scena nie je "riedka". Otazka - Mike Dailly .. https://www.youtube.com/@MikeDailly/videos .. uz minimalne pred rokom spominal, ze ma vlastny cacheovaci kod pre obidenie poctu sprite vzorov na 128 kusov pri 4 bitoch ..
díky za pochvalu... ;)
Mike Dailly: ano je to možné,... ale nezaregistroval jsem to. Ono ve výsledku k něčemu takovému vždy dojdeš, když děláš něco složitějšího ;)
Ide mi o nieco ine. "Kazdy" sa casom stazuje, ze 128 sprite vzorov je malo, preto zistujem ako sa to da obist .. M. Dailly to nejako robi ale nechcel nic blizsie vysvetlovat. Sprite RAM je vnutri FPGA RAM, nie v systemovej RAM. Nie som programator, takze ako sa to vlastne da urobit, DMA nejako krmi sprite RAM. Next DMA je stale iba jednokanalove, ze.
Však principielně to tam máš popsané jak to dělám - to co nepotřebuji použiji na něco jiného a když není místo, nechám tam sprite z minula...
ale ja nie som programator, potrebujem to viac polopate .. je teda mozne pouzit viac ako 128x4 bit sprite vzorov ale musis pouzit DMA na dodavanie dat do sprite RAM, ale kedze DMA je iba jednokanalove, tak sa tam niekde vojde napriklad aj DMA cas pre inu grafiku /fill alebo ine/ alebo DMA audio zaroven ? Viem, ze su tam rozne DMA rezimy, ale ako presne to funguje, ako laik netusim.
https://www.youtube.com/watch?v=Fis5OY8EdPw
In this demo i did a few years back ( github code linked ) . It used more than the 128 sprites. What I did was split the screen into regions using the vertical line interrupt ( multiple per screen ) i would start by uploading sprites 0 ->128 which would be ordered top to bottom. then every so often down the screen i would replace the sprites 0->63 , next time 64->127 ,again with 0->63 etc..
Thanks, that's a great trick, and you're right that 128 is only a per-moment limit: once the beam has passed a sprite, its slot (and even its pattern) can be reused further down. In this game it would help less than it looks, for three reasons. First, the scarce resource is pattern RAM rather than attribute slots: the player's car keeps 36 patterns, which leaves 92 for everything else, while the attribute list is capped at 120. Second, in a pseudo-3D racer everything stands on the ground, so the sprites aren't spread vertically: distant objects bunch up at the horizon, near trees are up to 256 px tall, and a 4× cell is 64 lines high, so most sprites would cross any band boundary. Third, sprite priority on the Next comes from the slot number, and I get depth ordering for free by filling the list near to far. Assigning slots top to bottom would fight that wherever sprites overlap. It might still work for the lower rows of near trees and the cars below the horizon, though. Maybe even driven by the copper through the sprite attribute nextregs. Worth an experiment!
Neřekl bych že 128 sprajtů je reálný limit. Pokud umí Next vykreslit alespoň 100 sprajtů na řádek (1600 pixelů!!) tak nevidím nejmenší problém. Tohle je fillrate o kterém se Mega Drivu či SNESu může jen zdálky zdát. Jasně je tam omezení v paměti a DMA ale pořád je to masivní fillrate. Daleko před 16-bitovými konzolemi.
nejde o pocet sprites ako taky .. sleduj .. ma hru, kde je iba jeden sprite, velky super animovany gundam robot, ktory ma dokopy 128 animacnych fazi. Pointa - nemas ziaden dalsi volny sprite pattern slot pre cokolvek ine na ploche. Sprite RAM je plna. Bud pouzijes CPU sprites, alebo nejaky DMA cache kod, nie som vsak progis, tak neviem.
jelikoz vždy potrebuješ ale zobrazit jen jednu fázi, ty ostatní můžeš použít na jiné sprity... myslím, že se tomu říká cliping (ale nejsem si jistý) - prostě sprity, které nejsou na obrazovce jsou pro tebe volné k nahrátí jiného spritu, který chceš zobrazit... je to pár bytu...
jen si musíš udržet informace, které sprity co predstavují (mnohdy je jedna postavička tvořena více sprity), tak musíš vědět, který sprite kam patří... a po zobrazení pokud ho již nepotřebuješ (protože se ti mezitím zobrazila další fáze z jiných slotů), tak je označíš jako nahraditelné. Fakt to není těžké...
ale asi o tom něco napíšu ;)
Samozrejme. Ide o to ako sa vlastne sa ta sprite RAM plni .. chces novu sadu sprite vzorov, tak to tam musi nahadzat DMA ? Ak mas zaroven DMA audio a DMA niekde robi fill pre grafiku, tak to cele musis ako programator nejako manazovat ? Je vlastne pocet sprite vzorov, myslim naozaj velke mnozstvo a ich dynamicka alokacia do sprite RAM .. velky problem ?
Nieco podobne sa mozem spytat aj pri TILE map rezime, kde je limit 512 tiles, ak nepouzivas ULA. Tych 512 unikatnych tiles minus off set farba, rotacia, mirror je tvrdy limit ??
https://www.youtube.com/watch?v=epk-wiQwg-A
vsetko o com pises vyssie .. tento chlapik proste zarezal .. pouziva zjavne HW sprites s plnym HW zoom, orezanie HW sprites na horizonte, proste to necha tak ako to je .. vyzera to ako za hodinu urobena AI hra bez velkeho "osetrovania" kodu ludskou rukou.
Svět se řítí do prdele...
přestává mě toto vše bavit :-( až se mi někdo příště někdo zeptá, proč neuveřejňuji zdrojové kódy, tak je to přesně kvůli tomuto... asi přestanu i psát tyto podrobné návody, protože od zveřejnění této série článku se objevily další 4 závodní hry,... takže já se tu potím, vymýšlím a oni jen vezmou tento text/zdroj. kódy a předhodí to AIčku...
Mike Cadwallader .. je blizko Next dev timu .. skusa dostat na Next slavny amigacky Lotus 1 .. trapi sa a priemernu mu to ide na 15 fps, pritom je aktivna presne polovica okna v hre jedneho hraca, ma pady pod 10 fps, ked engine renderuje "udolie" a citujem .. vycerpal vsetky optimalizacne triky. Nie som programator ale dovolim si zapochybovat.
https://www.youtube.com/watch?v=m8vYkmIGuJE
Já to vím, že má problémy, v posledním Next víkendovém týdeníku o tom psali... a dobře mu tak.
Mike je člověk, který mne nazval idiotem (pod příspěvkem, který jsem nepsal a jen někdo sdílel moji hru Angry Birds ve skupině ZX Spectrum Next...), takže mu to tak trošku přeji... ;)
Amen. A musis pisat dalej, heh .. som zvedavy co vymyslel ten demo sialenec ped7g !!
Taky jsem si všiml že se v poslední době nějak moc rojí hry na vše možné. Nebudeme si nic nalhávat, je to vibe coding. Asi je to lepší než kdyby někdo neustále dokola používal AGD, na druhou stranu už to nebude těch 10 vyvolených co bude umět něco nakódovat, a chápu že je to musí srát. Pokrok nezastavíš. Čas jít dělat něco smysluplnějšího...
Nastup AI bol nezadrzatelny a viditelny dekady dopredu. Ak by sme teraz skocili v case o dalsich 5 rokov, tak vam budu dnesne vase strachy, myslienky, dojmy a predstavy pripadat smiesne. Ta buduca AI zozerie akekolvek data sa jej dostanu pod ruky, ak budu dostupne na nete .. s neuveritelnymi vystupmi.
Je naozaj mozne, ze o 5 rokov pre AI napisete prompt: urob ten Lotus 2 pre Next a nech to beha lepsie ako na amige a do hodiny to bude hotove bez dalsieho vasho zasahu /ak mate dost tokenov/.
Co sa bude diat nasledujuce roky s "autorskymi pravami a intelekt. vlastnictvom" .. bude velka tema.