Optimalizovana varianta:
calc_pixel_value: ; parametry: ; B - x-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; A - hodnota pixelu push bc ; zapamatovat si hodnotu v registru B ld a, b ; A: X7 X6 X5 X4 X3 X2 X1 X0 and %00000111 ; A: 0 0 0 0 0 X2 X1 X0 ld b, a ; počitadlo smyčky (neměníme příznaky) ld a, %10000000 ; výchozí maska (neměníme příznaky) jr z, end_calc ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec next_shift: srl a ; posunout masku doprava djnz next_shift ; 1x až 7x end_calc: pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu
"srl a" je dvoubajtova instrukce na 8 taktu. Lepsi je pouzit "polovicni" "rrca".
Da se usetrit jeste jeden bajt kdyz se vyhodi podmineny skok a nahradi za inc a zmenime masku.
calc_pixel_value: ; parametry: ; B - x-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; A - hodnota pixelu if 0 push bc ; 1:11 zapamatovat si hodnotu v registru B ld a, b ; 1:4 A: X7 X6 X5 X4 X3 X2 X1 X0 and %00000111 ; 2:7 A: 0 0 0 0 0 X2 X1 X0 ld b, a ; 1:4 počitadlo smyčky inc b ; 1:4 počitadlo smyčky + 1 ld a, %00000001 ; 2:7 výchozí maska rrca ; 1:4 posunout masku doprava djnz $-1 ; 2:8/13 1x až 8x pop bc ; 1:10 obnovit hodnotu v registru B ret ; 1:10 návrat z podprogramu else ld a, b ; 1:4 A: X7 X6 X5 X4 X3 X2 X1 X0 and %00000111 ; 2:7 A: 0 0 0 0 0 X2 X1 X0 cpl ; 1:4 xor FF rlca ; 1:4 rlca ; 1:4 rlca ; 1:4 ld ($+5),A ; 3:13 xor A ; 1:4 set 0,A ; 2:8 ret ; 1:10 návrat z podprogramu endif
A nebo zvolit trosku silenou (ne kazdeho tohle napadne) variantu, s nemenym casem a samomodifikujicim se kodem.
21. 3. 2023, 04:54 editováno autorem komentáře
tyjo jsem Atarista a znam docela dost dobre MOS 6502. Ale tady je videt, ze Z80 je o dost vic komplikovany. Tedy ne, ze by mel nejake slozite instrukce nebo chovani, ale zda se mi, ze je tady vic moznosti jak neco napsat.
Asi to ve vysledku byla vetsi zabava v tom psat, tezko po tech letech posoudit :)
PS: 6502 mela zase brutalnejsi adresovaci rezimy, ale porat to bylo +- "ortogonalni".
Pokud drzime adresu v HL tak muzeme pouzit efektivnejsi variantu:
plot: ; třetí varianta podprogramu pro vykreslení pixelu ; ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) call calc_pixel_address ; výpočet adresy pixelu call calc_pixel_value ; výpočet ukládané hodnoty if 0 ld d, (hl) ; 1:7 přečíst původní hodnotu osmice pixelů or d ; 1:4 použít vypočtenou masku pro nastavení jediného bitu else or (hl) ; 1:7 přečíst původní hodnotu osmice pixelů ; použít vypočtenou masku pro nastavení jediného bitu endif ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu
U toho vypisu matrixu znaku
draw_ascii_table: ; Vytištění ASCII tabulky ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu
bych postupoval jinak. Prohodil bych mezeru na zacatek jako prvni znak. tim bych presunul problem prelezu pres tretiny na rutinu draw_char a mezeru bych "tisknul" zvednutim indexu E.
Pak kdyz se podivame jak ukoncujeme smycku tak zjistime ze to jde nahradit pres sign flag, takze zadne cp 128 neni potreba. Celych 5 bajtu dolu, nebo 1 bajt kdyz jen vyhodime cp.
draw_ascii_table: ; Vytištění ASCII tabulky ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: inc e ; "vykreslit" pred znakem mezeru push af ; uschovat akumulátor na zásobník call draw_char ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku jp p, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu
Jedna z nejzaludnejsich veci na Z80. Naucit se spravne kdy se vlajky a ktere nastavuji a kdy naopak ne. To pak uplne meni co pisete za kod. Ze se vlajka nemeni umoznuje instrukci pouzit "na uklid" a naopak ji to muze delat nepouzitelnou.
Bylo by zajimave vedet proc to tak navrhli, protoze to je neco co je tezke vymyslet do dusledku. Mozna to byla jen postupna evoluce.
PS: Mit jinak zprehazene bity ve F by umoznilo nejake osklive triky. To je asi spis problem, kdyz se zacnete divat prilis dlouho do propasti...
Ale to je asi taky dusledek zachovani kompatibility a ten puvodni navrh asi byl rad ze je rad a neresil, ze pouzit v djnz registr C by bylo fakt lepsi.
Hmm... prave koukam ze to asi navrhl puvodne ital Federico Faggin a je mu ted 81.
A misto kompatibility je to teda spis evoluce 4004 (jeho navrh)->8008(sefuje tomu)->8080(sefuje tomu)->Z80(sefuje tomu)
To jsem si myslel ze v tom maji prsty hlavne japonci. Masatoshi Shima. Tomu je 79.
Treti do party 4004 Ralph Ungermann uz teda neni mezi nami. Ale vtipne cteni ze opustil intel kvuli nizkym mzdam.
tak u 6502 to udelali jinak - flagy se v podstate nastavuji kdekoli to jde, tedy i u LDx atd. Ma to svoje vyhody, protoze se prakticky nikdy nemusi pouzit CMP, na druhou stranu se moc flagy nedaji pouzit dlouhodobejsi ukladani hodnot. IMHO to proste u obou "rad" procesoru tipli (a hlavne se ty CPU zacaly pouzivat v uplne jinych oblastech, nez se predpokladalo).
A nejtezsi cast nakonec. Prevod YX na adresu v HL. Najit efektivni cestu je tak slozity, ze jsem se rovnou podival jak jsem to resil nekdy predtim... .) Taky jsou v mem pasmu 4 rano... .)
https://github.com/DW0RKiN/M4_FORTH/blob/master/M4/graphic_runtime.m4
Po "drobne" uprave se da usetrit rovnych 6 bajtu!
calc_pixel_address: ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; HL - adresa pro zápis pixelu ; ; pozměněné registry: ; A ; ; vzor adresy: ; 0 1 0 Y7 Y6 Y2 Y1 Y0 | Y5 Y4 Y3 X4 X3 X2 X1 X0 if 1 ld A, C ; 1:4 bbrrrsss = Y or 0x01 ; 2:7 bbrrrss1 carry = 0 rra ; 1:4 0bbrrrss carry = 1 rra ; 1:4 10bbrrrs carry = s? and A ; 1:4 10bbrrrs carry = 0 rra ; 1:4 010bbrrr ld L, A ; 1:4 .....rrr xor C ; 1:4 ???????? and 0xF8 ; 2:7 ?????000 xor C ; 1:4 010bbsss ld H, A ; 1:4 H = 64+8*INT (b/64)+(b mod 8) ld A, L ; 1:4 .....rrr xor B ; 1:4 ???????? provede se 2x takze zadna zmena, mezitim ale vynulujeme hornich 5 bitu and 0x07 ; 2:7 00000??? xor B ; 1:4 cccccrrr rrca ; 1:4 rcccccrr rrca ; 1:4 rrcccccr rrca ; 1:4 rrrccccc ld L, A ; 1:4 L = 32*INT (b/(b mod 64)/8)+INT (x/8). ret ; 1:10 návrat z podprogramu else ld a, c ; všech osm bitů Y-ové souřadnice and %00000111 ; pouze spodní tři bity y-ové souřadnice (Y2 Y1 Y0) ; A: 0 0 0 0 0 Y2 Y1 Y0 or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld h, a ; část horního bajtu adresy je vypočtena ; H: 0 1 0 0 0 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rra rra rra ; rotace doprava -> Y1 Y0 xx Y7 Y6 Y5 Y4 Y3 and %00011000 ; zamaskovat ; A: 0 0 0 Y7 Y6 0 0 0 or h ; a přidat k vypočtenému mezivýsledku ld h, a ; H: 0 1 0 Y7 Y6 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rla rla ; A: Y5 Y4 Y3 Y2 Y1 Y0 xx xx and %11100000 ; A: Y5 Y4 Y3 0 0 0 0 0 ld l, a ; část spodního bajtu adresy je vypočtena ld a, b ; všech osm bitů X-ové souřadnice rra rra rra ; rotace doprava -> 0 0 0 X7 X6 X5 X4 and %00011111 ; A: 0 0 0 X7 X6 X5 X4 X3 or l ; A: Y5 Y3 Y3 X7 X6 X5 X4 X3 ld l, a ; spodní bajt adresy je vypočten ret ; návrat z podprogramu endif
Tak nějak se mi potvrzuje hláška kolegy sinclairisty před 35 lety - "grafika na Sinclairu je peklo". Pamatuju, že díky "lineárnímu" mapování VRAM na C64 se při unrollingu všech 4 variant vykreslování obecné přímky podle Bresenhama vystačilo snad s 6 instrukcemi na pixel v 320 x 200 (jo, požralo to par kB).
Imho je tohle spis ukazka jak lze obtizne psat na Z80.
Ale ta grafika je podle me teda peklo taky. Zjednodusenim v jednom smeru, to ve vsech ostatnich udelali slozitejsi...
Preferoval bych, kdyby to slo pekne za sebou:
+256 o 8 pixelu dolu a nebo -256 o 8 pixelu nahoru.
+32 o pixel dolu a nebo -32 (+FFE0) o pixel nahoru.
Nemusi se resit zadne up/down rutiny, kdyz pisete uz sam o sobe nejaky komplikovany algoritmus na kruznici/usecku.
Znaky se komplikuji na to, ze problem prechodu tretin se meni na problem prechodu radku.
Na radku je to stale jen +-1 pro spodni bajt. Jakmile to preleze pres 5 bitu tak se meni +-horni bajt.
Neco jako pro znak "vpravo/nasledujici znak/+1":
L = X
L |= 0xC0
H = Y
HL++
L &= 0x1F
A pro znak "vlevo/predchozi/-1":
L = X
H = Y
HL--
L &= 0x1F
Timhle se da vlastne resit prechod pro tretiny i ted. U H |=3 a H &=F8.
TAB x, ktere nastavi kurzor na x znak na radku (pouhe prirazeni) a nebo pokud je to posun doleva tak snizi radek (+256) by byl o dost snazsi.
YX ve znacich na adresu v HL:
H = Y
L = X
add HL, HL
add HL, HL
add HL, HL
YX v pixelech na adresu v HL?
H = Y
L = X
a ted potrebujeme HL >>= 3
takze asi neco jako
A = L
xor H
and 0xF8
xor H
rrca
rrca
rrca
L = A
A = H
rrca
rrca
rrca
H = A
Hmm to je 12+2 bajtu (12*4+7 = 55 taktu)
Takze na 12 bajtu (3*16 = 48 taktu) to jde udelat primo pres shift H a L.
PS: Mit to linearne by pro me asi nejvic zmenilo ten pocit z pomaleho nacitani obrazovky her. Pokud by to neemulovala primo ta nacitaci rutina.
Souhlas, v minulosti jsem nechápal, proč měl Sinclair takto (z mého pohledu) komplikovanou VRAM. Já jsem měl Commodore Plus/4 a tam byla VRAM lineární. Používalo se několik různých režimu (vše zajišťoval obvod TED):
Character: 40x25 znaků + volně měnitelná znaková sada v RAM/ROM (hw kurzor, flash, inverzní znaky).
Bitmap: 320x200
Bitmap Multicolor (MCM): 160x200
plus ještě existoval režim Extended Color Mode.
+ hw scrolling.
Všechny režimy byly lineární a jejích umístění na stránku v RAM se dalo měnit, takže jste mohli používat double-buffering. Žádné zpomalení běhu programů v určité části RAM nebylo (jako to má ZX Spectrum).
A programování v MOS 6502 bylo velmi jednoduché (oproti Zilog Z80).
Takže obdivuji všechny Spektristy. Kudos!
Reaguji na použití cc65 https://github.com/cc65/cc65, jiné cross-překladače C jsem nezkoušel. Odpověděl jste si sám uvedením toho nejpodstatnějšího. :-)
Kdysi jsem na A8 dělal textovku. Chtěl jsem k tomu udělat nějaký obecný engine a do něj jen "nakonfigurovat" data pro konkrétní hru. Začal jsem v Basicu, velmi brzy došla paměť. Zkusil TurboBasic, došla paměť. Zkusil cc65, došla paměť ještě dříve, asi proto, že jsem ten vestavěný Basic uměl používat efektivněji. Nakonec to dopadlo dle očekávání: assembler. A ano, nejnáročnější část byl "framework" pro předávání parametrů.
Podle mého názoru je cc65 nepoužitelné pro projekty větší než velmi malé. Myslím, že Action! https://atariwiki.org/wiki/Wiki.jsp?page=Action by byla lepší volba, ale moje pirátská kopie (cca v r. 1988) padala, takže je to jen odhad.
jo to cecko (no dobre C++, ale ani tehdy to nesplnovalo normu :) bylo pomaly, ale Placal se prekladal prakticky hned (kdyz byl preklad do RAM). Oproti osmibitum vetsinou bez disketovky (a bez HDD) fakt minimalne o tridu jinde (vlastne uz nikdy potom jsem nezazil takovy skok v moznostech nejakeho vyvojoveho nastroje).
Jojo, přesně moje pocity. První kompl 386SX, Borlandí IDE s krokováním a inspektorem proměnných bylo pro mě úplné zjevení z jiného vesmíru. :-) K tomu "refaktorizace" pomocí změn řetězců textu pomocí regulérních výrazů, to byla bomba. A když jsem upgradoval z Helcules na VGA a zjistil, že mohou být v komplu obě videokarty, měl jsem dvoumonitorový stroj. Na jednom monitoru IDE, na druhém laděný program. Naprostá bomba! :-)
Smekam pred vama vsema co se v tom asm Z80 (/jinem) hrabete. Nez v tom clovek neco vyrobi tak uplyne cela vecnost. Vzpominam si jak jsem v mladi cely den hledal chybu v jednom bitu v rutine, ktera na ZX vertikalne scrolovala bitmapu (valec hraciho automatu). Myslim ze techto lidi je malo a jsou nezbytne potreba, aby ty stroje ozily. Dneska uz to mladsi programatori podle me moc nechapou...