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