Obsah
1. Vývoj her a grafických i zvukových dem pro ZX Spectrum: vlastní vykreslovací subrutiny (3)
2. Vykreslení několika sad ASCII tabulek na obrazovku ZX Spectra
3. Úplný zdrojový kód demonstračního příkladu pro tisk ASCII tabulek
4. První varianta podprogramu typu PLOT pro vykreslení pixelu na obrazovku
5. Výpočet adresy bajtu s obsahem pixelu pro zadané souřadnice
7. Pomalé vykreslení úsečky složené z „pixelů“
8. Kód pro vykreslení šikmé úsečky z jednotlivých blokových „pixelů“
9. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „blokových pixelů“
11. Realizace výpočtu masky pixelu
12. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „pravých pixelů“
13. Zjednodušení výpočtu masky pixelu
14. Úplný zdrojový kód upraveného demonstračního příkladu
15. Vykreslení pixelů na libovolném pozadí
16. Korektní zápis masky pixelu tak, aby nedošlo ke smazání pozadí
17. Úplný zdrojový kód dnešního posledního demonstračního příkladu
18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů
19. Repositář s demonstračními příklady
1. Vývoj her a grafických i zvukových dem pro ZX Spectrum: vlastní vykreslovací subrutiny (3)
V pořadí již šesté části seriálu o vývoji programů pro legendární osmibitový domácí mikropočítač ZX Spectrum se vrátíme k tématu, kterému jsme se začali věnovat již minule, a to konkrétně v patnácté kapitole. Řekli jsme si, že již relativně dobře umíme na obrazovku ZX Spectra vypsat znak či celý řetězec. Dokonce máme k dispozici několik metod, jak toho dosáhnout. Zejména můžeme využít subrutinu zapsanou přímo v ROM ZX Spectra (ta umí kromě dalších věcí rozeznávat řídicí kódy) nebo můžeme zavolat naši subrutinu, která je sice původně navržena pro tisk znaků v masce 8×8 znaků, ale relativně snadno ji lze upravit na různé výšky znaků (složitější bude úprava pro různé šířky).
Ovšem zajímavější (i když možná méně praktické) bude zjistit, jakým způsobem je možné realizovat operaci typu PLOT. Jedná se o standardní příkaz Sinclair BASICu sloužící pro vykreslení jediného pixelu (a navíc pro zapamatování souřadnice vykreslení pixelu pro další operace, například pr vykreslení úsečky). Aby byla situace nepatrně zajímavější, existuje tento příkaz v několika variantách:
Příkaz | Stručný popis příkazu |
---|---|
PLOT x,y | vykreslení pixelu na souřadnice [x,y] barvou inkoustu (ink) |
PLOT INVERSE 1; x, y | vykreslení pixelu barvou papíru (paper) |
PLOT OVER 1; x, y | negace barvy pixelu |
PLOT INVERSE 1;OVER 1; | pouze přesune grafický „kurzor“ na nové souřadnice |
Při realizaci naší vlastní varianty příkazu PLOT budeme pochopitelně postupovat krok za krokem. Konkrétně si dnes ukážeme několik demonstračních příkladů, které budou postupně realizovat následující funkce (realizované formou podprogramů – subrutin):
- Subrutina pro vykreslení ASCII tabulky, kterou použijeme později pro vyplnění pozadí obrazovky, aby bylo jasně patrné, jak pracuje námi realizovaný příkaz PLOT.
- Výpočet adresy bajtu v obrazové paměti, kam se má pixel vykreslit.
- Vykreslení „širokého“ pixelu o šířce osmi obrazových pixelů.
- Výpočet bitové masky pixelu, protože každý pixel je zapsán jako jediný bit uložený společně s dalšími sedmi pixely v bajtu.
- Vykreslení „úzkého“ pixelu na korektní místo na obrazovce.
- Zajištění, že se nesmaže pozadí celé osmice pixelů, na tom místě na obrazovce, kam se provádí zápis.

Obrázek 1: Obchodní část hry Elite přepsaná do Sinclair BASICu. O vykreslení celé obrazovky se z velké části starají standardní grafické příkazy Sinclair BASICu.
2. Vykreslení několika sad ASCII tabulek na obrazovku ZX Spectra
Ještě předtím, než se budeme zabývat subrutinou pro vykreslení pixelu, si ukážeme program, který po svém spuštění vykreslí na obrazovku několik ASCII tabulek. Tento program budeme potřebovat proto, abychom zaplnili plochu pozadí nějakým vzorkem, na němž bude patrné, zda vykreslovací rutina PLOT pracuje korektně či nikoli. Výsledná obrazovka by měla vypadat následovně:

Obrázek 2: Několik ASCII tabulek, které vyplňují
Činnost celého programu je založena na podprogramu, který volá nám již známý podprogram draw_char, který vytiskne jeden znak a následně posune hodnotu uloženou v registrovém páru DE na adresu v obrazové paměti, kam se má vykreslit další znak. V jedné iteraci vykreslíme dvojici znaků – nějaký znak s kódem uloženým v počitadlu smyčky a mezeru:
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
Vykreslení několika ASCII tabulek přes celou obrazovku je již snadné, protože nám postačuje zavolat výše popsanou subrutinu draw_ascii_table čtyřikrát za sebou, přičemž nebudeme zasahovat do adresy uložené v registrovém páru DE:
fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ret ; návrat z podprogramu
Samotné tělo programu se tedy zkrátí na zavolání subrutiny fill_in_screen následované vstupem do nekonečné smyčky (aby se řízení nevrátilo zpět do BASICu):
; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami finito: jr finito ; ukončit program nekonečnou smyčkou
3. Úplný zdrojový kód demonstračního příkladu pro tisk ASCII tabulek
Úplný zdrojový kód demonstračního příkladu popsaného ve druhé kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/50-ascii-table.asm:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami finito: jr finito ; ukončit program nekonečnou smyčkou fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ret ; návrat z podprogramu 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 draw_char: ; Vytištění jednoho znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Podívejme se ještě na způsob překladu tohoto příkladu do strojového kódu. Jeho celková délka dosahuje šedesáti bajtů:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:CD0580 CALL 8005 8003: label finito 8003:18FE JR 8003 8005: label fill_in_screen 8005:110040 LD DE, 4000 8008:CD1580 CALL 8015 800B:CD1580 CALL 8015 800E:CD1580 CALL 8015 8011:CD1580 CALL 8015 8014:C9 RET 8015: label draw_ascii_table 8015:3E20 LD A, 20 8017: label next_char 8017:F5 PUSH AF 8018:CD2780 CALL 8027 801B:3E20 LD A, 20 801D:CD2780 CALL 8027 8020:F1 POP AF 8021:3C INC A 8022:FE80 CP 80 8024:20F1 JR NZ, 8017 8026:C9 RET 8027: label draw_char 8027:01003C LD BC, 3C00 802A:61 LD H, C 802B:6F LD L, A 802C:29 ADD HL, HL 802D:29 ADD HL, HL 802E:29 ADD HL, HL 802F:09 ADD HL, BC 8030:0608 LD B, 08 8032:4A LD C, D 8033: label loop 8033:7E LD A, (HL) 8034:12 LD (DE), A 8035:2C INC L 8036:14 INC D 8037:10FA DJNZ 8033 8039:1C INC E 803A:C8 RET Z 803B:51 LD D, C 803C:C9 RET 803D: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 803C
4. První varianta podprogramu typu PLOT pro vykreslení pixelu na obrazovku
Vraťme se k ústřednímu tématu dnešního článku – k implementaci podprogramu, který by měl být obdobou příkazu PLOT, tj. který by měl provést vykreslení jednoho pixelu na obrazovku. Připomeňme si, že rozlišení obrazovky ZX Spectra je 256×192 pixelů, což znamená, že x-ová i y-ová souřadnice pixelu musí být reprezentována osmibitovým číslem (což je oproti počítačům s rozlišením 320×xxx pixelů značné zjednodušení). X-ovou souřadnici budeme do postupně vznikajícího podprogramu předávat v registru B, y-ovou souřadnici pak v registru C.
Nejprimitivnější podoba podprogramu PLOT na základě obsahu pracovních registrů B a C (souřadnic pixelu) vypočítá adresu pixelu (resp. celé osmice pixelů) v obrazové paměti a následně na tuto adresu uloží konstantu 0×ff. To vlastně znamená, že nevykreslíme jediný pixel, ale osmici sousedních pixelů. Nějak však začít musíme :)
Podprogram bude vypadat následovně:
plot: ; první 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 ld (hl), 0xff ; zápis "něčeho" na adresu pixelu ret ; návrat z podprogramu
5. Výpočet adresy bajtu s obsahem pixelu pro zadané souřadnice
Výpočet adresy v obrazové paměti, na níž má být zápis proveden, je relativně komplikované, ovšem na druhou stranu se vyhneme nutnosti násobení atd. (popř. náhradě násobení za posuny a součty). Ve třetí části tohoto seriálu jsme si popsali organizaci obrazové paměti. Připomeňme si, že adresa každého bajtu obrazové paměti se skládá z několika bitových polí v tomto formátu (jedná se o 16bitovou adresu zapsanou formou jednotlivých bitů):
010 BB SSS RRR CCCCC BB: číslo bloku 0,1,2 (v bloku číslo 3 je atributová paměť) SSS: číslo řádky v jednom znaku, který je vysoký osm obrazových řádků RRR: pozice textového řádku v bloku. Každý blok je vysoký 64 obrazových řádků, což odpovídá osmi řádkům textovým CCCCC: index sloupce bajtu v rozmezí 0..31, kde je uložena osmice sousedních pixelů
My ovšem nyní nepracujeme se znaky, ale s pixely. Schéma adresy se ovšem změní jen nepatrně (opět je adresa uvedena po jednotlivých bitech):
010 Y7 Y6 Y2 Y1 Y0 | Y5 Y3 Y3 X7 X6 X5 X4 X3
Povšimněte si, že nyní nejvyšší dva bity y-ové souřadnice uvádí číslo bloku. Naopak se v adrese vůbec nevyskytují bity X2, X1 a X0, což je logické, protože tyto tři bity specifikují index bitu v zapisovaném bajtu (viz navazující kapitoly). Našim úkolem je tedy realizovat tento algoritmus:
- Prvním vstupem jsou hodnoty X7 X6 X5 X4 X3 X3 X1 X0 uložené v registru B
- Druhým vstupem jsou hodnoty Y7 Y6 Y5 Y4 Y3 Y3 Y1 Y0 uložené v registru C
- Výstupem je 16bitová adresa 010 Y7 Y6 Y2 Y1 Y0 Y5 Y3 Y3 X7 X6 X5 X4 X3
- Vyšší bajt adresy 010 Y7 Y6 Y2 Y1 Y0 bude uložen do registru H
- Nižší bajt adresy Y5 Y3 Y3 X7 X6 X5 X4 X3 bude uložen do registru L
6. Realizace výpočtu adresy
Výše uvedený algoritmus je možné realizovat bitovými operacemi (and, or) a bitovými posuny (rra). Realizace celého algoritmu se „vejde“ do 29 bajtů (a to jsme neprováděli žádné optimalizace):
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 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
7. Pomalé vykreslení úsečky složené z „pixelů“
Aby bylo patrné, jak dobře či špatně se nám daří vypočítat adresu vykreslovaných pixelů, pokusíme se na obrazovku vykreslit šikmou úsečku (pod úhlem 45°). Úsečku přitom vykreslíme zpomaleně – mezi vykreslení každého dalšího pixelu bude vložena zpožďovací rutina. Vzhledem k relativně vysoké rychlosti mikroprocesoru Zilog Z80 využívá tato rutina dvojici vnořených programových smyček, každou s osmibitovým čítačem (jedna smyčka s osmibitovým čítačem by nestačila). Jedna z možných variant této zpožďovací smyčky může vypadat takto:
delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu
8. Kód pro vykreslení šikmé úsečky z jednotlivých blokových „pixelů“
Samotné vykreslení šikmé úsečky je již jednoduché, protože nám postačuje postupně modifikovat hodnoty pracovních registrů B a C, které jsou použity při volání podprogramu PLOT a které obsahují x-ovou a y-ovou souřadnici pixelu. Jak již víme, budeme vkládat mezi jednotlivá volání subrutiny plot zpožďovací smyčky:
; Vstupní bod celého programu start: ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou
Výsledek bude vypadat zhruba následovně:

Obrázek 3: Postupné vykreslování úsečky z „blokových pixelů“.

Obrázek 4: Postupné vykreslování úsečky z „blokových pixelů“.

Obrázek 5: Postupné vykreslování úsečky z „blokových pixelů“.

Obrázek 6: Postupné vykreslování úsečky z „blokových pixelů“.
9. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „blokových pixelů“
Úplný zdrojový kód demonstračního příkladu popsaného v předchozích kapitolách je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/51-plot-block.asm a vypadá takto:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu plot: ; první 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 ld (hl), 0xff ; zápis "něčeho" na adresu pixelu ret ; návrat z podprogramu 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 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 end ENTRY_POINT
Po překladu do strojového kódu získáme sekvenci instrukcí s délkou pouhých 65 bajtů:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:0600 LD B, 00 8002:0E00 LD C, 00 8004: label loop 8004:CD1F80 CALL 801F 8007:CD1380 CALL 8013 800A:04 INC B 800B:0C INC C 800C:78 LD A, B 800D:FEC0 CP C0 800F:20F3 JR NZ, 8004 8011: label finito 8011:18FE JR 8011 8013: label delay 8013:C5 PUSH BC 8014:0614 LD B, 14 8016: label outer_loop 8016:0E00 LD C, 00 8018: label inner_loop 8018:0D DEC C 8019:20FD JR NZ, 8018 801B:10F9 DJNZ 8016 801D:C1 POP BC 801E:C9 RET 801F: label plot 801F:CD2580 CALL 8025 8022:36FF LD (HL), FF 8024:C9 RET 8025: label calc_pixel_address 8025:79 LD A, C 8026:E607 AND 07 8028:F640 OR 40 802A:67 LD H, A 802B:79 LD A, C 802C:1F RRA 802D:1F RRA 802E:1F RRA 802F:E618 AND 18 8031:B4 OR H 8032:67 LD H, A 8033:79 LD A, C 8034:17 RLA 8035:17 RLA 8036:E6E0 AND E0 8038:6F LD L, A 8039:78 LD A, B 803A:1F RRA 803B:1F RRA 803C:1F RRA 803D:E61F AND 1F 803F:B5 OR L 8040:6F LD L, A 8041:C9 RET 8042: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8041
10. Výpočet masky pixelu
Abychom nekreslili pouze „široké pixely“, ale dokázali skutečně vykreslit jednotlivý pixel, musíme nejdříve vypočítat masku pixelu. Jedná se o bajt, který obsahuje pouze jediný nastavený bit vypočtený na základě nejnižších tří bitů X-ové souřadnice (tyto tři bity jsme prozatím ignorovali). Výpočet je snadný za předpokladu, pokud si uvědomíme, jak má maska vypadat:
X2 X1 X0 Maska 0 0 0 10000000 0 0 1 01000000 0 1 0 00100000 0 1 1 00010000 1 0 0 00001000 1 0 1 00000100 1 1 0 00000010 1 1 1 00000001
Vidíme, že se vlastně jedná o posun bitové hodnoty 1000000 doprava o n bitů, přičemž n odpovídá spodním třem bitům x-ové souřadnice od 0 do 7.
Jakmile je maska vypočtena, můžeme ji (prozatím chybně) uložit do obrazové paměti takto:
plot: ; druhá 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 ld (hl), a ; zápis hodnoty pixelu ret ; návrat z podprogramu
Výsledek bude vypadat zhruba následovně:

Obrázek 7: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 8: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 9: Postupné vykreslování úsečky z „úzkých pixelů“.
11. Realizace výpočtu masky pixelu
Podívejme se nyní na neoptimalizovaný program, který vypočte masku pixelu na základě plné osmibitové x-ové souřadnice, která je předána v registru B. Nejprve se z této souřadnice získají spodní tři bity a následně se použijí jako počitadlo smyčky, v níž je realizován posun bitové hodnoty 10000000 doprava:
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 jr z, put_1 ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec ld b, a ; počitadlo smyčky ld a, %10000000 ; výchozí maska next_shift: srl a ; posunout masku doprava djnz next_shift ; 1x až 7x pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu put_1: ld a, %10000000 ; výchozí maska je současně i návratovou hodnotou pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu
12. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „pravých pixelů“
Úplný zdrojový kód demonstračního příkladu popsaného v předchozích dvou kapitolách je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/52-plot-pixel.asm a vypadá takto:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu plot: ; druhá 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 ld (hl), a ; zápis hodnoty pixelu ret ; návrat z podprogramu 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 jr z, put_1 ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec ld b, a ; počitadlo smyčky ld a, %10000000 ; výchozí maska next_shift: srl a ; posunout masku doprava djnz next_shift ; 1x až 7x pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu put_1: ld a, %10000000 ; výchozí maska je současně i návratovou hodnotou pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu 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 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 end ENTRY_POINT
A pro úplnost si ukažme způsob překladu do strojového kódu, přičemž výsledek bude mít stále přijatelných 86 bajtů:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:0600 LD B, 00 8002:0E00 LD C, 00 8004: label loop 8004:CD1F80 CALL 801F 8007:CD1380 CALL 8013 800A:04 INC B 800B:0C INC C 800C:78 LD A, B 800D:FEC0 CP C0 800F:20F3 JR NZ, 8004 8011: label finito 8011:18FE JR 8011 8013: label delay 8013:C5 PUSH BC 8014:0614 LD B, 14 8016: label outer_loop 8016:0E00 LD C, 00 8018: label inner_loop 8018:0D DEC C 8019:20FD JR NZ, 8018 801B:10F9 DJNZ 8016 801D:C1 POP BC 801E:C9 RET 801F: label plot 801F:CD3A80 CALL 803A 8022:CD2780 CALL 8027 8025:77 LD (HL), A 8026:C9 RET 8027: label calc_pixel_value 8027:C5 PUSH BC 8028:78 LD A, B 8029:E607 AND 07 802B:2809 JR Z, 8036 802D:47 LD B, A 802E:3E80 LD A, 80 8030: label next_shift 8030:CB3F SRL A 8032:10FC DJNZ 8030 8034:C1 POP BC 8035:C9 RET 8036: label put_1 8036:3E80 LD A, 80 8038:C1 POP BC 8039:C9 RET 803A: label calc_pixel_address 803A:79 LD A, C 803B:E607 AND 07 803D:F640 OR 40 803F:67 LD H, A 8040:79 LD A, C 8041:1F RRA 8042:1F RRA 8043:1F RRA 8044:E618 AND 18 8046:B4 OR H 8047:67 LD H, A 8048:79 LD A, C 8049:17 RLA 804A:17 RLA 804B:E6E0 AND E0 804D:6F LD L, A 804E:78 LD A, B 804F:1F RRA 8050:1F RRA 8051:1F RRA 8052:E61F AND 1F 8054:B5 OR L 8055:6F LD L, A 8056:C9 RET 8057: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8056
13. Zjednodušení výpočtu masky pixelu
Způsob výpočtu masky pixelu, který jsme si ukázali v jedenácté kapitole, je možné různými způsoby zjednodušit. Jedna z možných variant zjednodušení spočívá v odstranění duplicitních instrukcí pop bc a ret a současně i naplnění masky hodnotou %10000000 pro nulové souřadnice X2, X1, X0. Postačuje nám nepatrně upravit programovou logiku, kdy počáteční hodnotu masky %10000000 vložíme do registru A a teprve poté testujeme, jestli nejsou hodnoty X2, X1 a X0 nulové – to nám Zilog Z80 skutečně umožňuje, protože instrukce ld nenastavují příznaky (na rozdíl od MOSu 6502, kde by tento postup nešel použít). Upravená subrutina pro výpočet masky pixelu vypadá následovně:
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
Výsledné chování programu se tím nijak nezmění:

Obrázek 10: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 11: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 12: Postupné vykreslování úsečky z „úzkých pixelů“.
14. Úplný zdrojový kód upraveného demonstračního příkladu
Úplný zdrojový kód upraveného demonstračního příkladu popsaného v předchozí kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/53-plot-pixel.asm a vypadá takto:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu plot: ; druhá 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 ld (hl), a ; zápis hodnoty pixelu ret ; návrat z podprogramu 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 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 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 end ENTRY_POINT
Po překladu do strojového kódu snadno zjistíme, že jsme ušetřili čtyři bajty. To se sice na první pohled nezdá moc, ale jedná se o celá 4% celkové velikosti kódu:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:0600 LD B, 00 8002:0E00 LD C, 00 8004: label loop 8004:CD1F80 CALL 801F 8007:CD1380 CALL 8013 800A:04 INC B 800B:0C INC C 800C:78 LD A, B 800D:FEC0 CP C0 800F:20F3 JR NZ, 8004 8011: label finito 8011:18FE JR 8011 8013: label delay 8013:C5 PUSH BC 8014:0614 LD B, 14 8016: label outer_loop 8016:0E00 LD C, 00 8018: label inner_loop 8018:0D DEC C 8019:20FD JR NZ, 8018 801B:10F9 DJNZ 8016 801D:C1 POP BC 801E:C9 RET 801F: label plot 801F:CD3680 CALL 8036 8022:CD2780 CALL 8027 8025:77 LD (HL), A 8026:C9 RET 8027: label calc_pixel_value 8027:C5 PUSH BC 8028:78 LD A, B 8029:E607 AND 07 802B:47 LD B, A 802C:3E80 LD A, 80 802E:2804 JR Z, 8034 8030: label next_shift 8030:CB3F SRL A 8032:10FC DJNZ 8030 8034: label end_calc 8034:C1 POP BC 8035:C9 RET 8036: label calc_pixel_address 8036:79 LD A, C 8037:E607 AND 07 8039:F640 OR 40 803B:67 LD H, A 803C:79 LD A, C 803D:1F RRA 803E:1F RRA 803F:1F RRA 8040:E618 AND 18 8042:B4 OR H 8043:67 LD H, A 8044:79 LD A, C 8045:17 RLA 8046:17 RLA 8047:E6E0 AND E0 8049:6F LD L, A 804A:78 LD A, B 804B:1F RRA 804C:1F RRA 804D:1F RRA 804E:E61F AND 1F 8050:B5 OR L 8051:6F LD L, A 8052:C9 RET 8053: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8052
15. Vykreslení pixelů na libovolném pozadí
Nyní si předchozí demonstrační příklad, který již (alespoň zdánlivě) dokáže vykreslit pixely na obrazovku korektním způsobem, nepatrně upravíme. Použijeme totiž podprogram fill_in_screen, kterým jsme se zabývali v první části dnešního článku, tak, aby se ještě před vykreslením úsečky z pixelů zaplnilo celé pozadí ASCII znaky. Celý program je složen ze série podprogramů, takže jeho úprava bude snadná:
; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou
Výsledek však ukáže, že jsme ještě stále nedošli ke kýženému výsledku, protože se vždy smaže překreslí celý blok osmi pixelů. Ve výsledku se tak jakoby smažou všechny znaky ležící na diagonále:

Obrázek 13: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.

Obrázek 14: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.

Obrázek 15: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.

Obrázek 16: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.
Pro úplnost si ukažme celý zdrojový kód takto upraveného demonstračního příkladu:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu plot: ; druhá 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 ld (hl), a ; zápis hodnoty pixelu ret ; návrat z podprogramu 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 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 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 fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ret ; návrat z podprogramu 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 draw_char: ; Vytištění jednoho znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop2: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop2 ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
16. Korektní zápis masky pixelu tak, aby nedošlo ke smazání pozadí
Původní algoritmus příkazu PLOT nejprve zjistil adresu, na kterou se má zapsat bitová maska a následně tuto bitovou masku vypočítal. Následně se celá bitová maska uložila na vypočtenou adresu, což ovšem znamenalo, že se vždy nastavil (na barvu inkoustu) jediný pixel z celé osmice, zatímco ostatních sedm pixelů bylo smazáno na barvu papíru. Toto „vykreslení+smazání“ je realizováno instrukcí ld (hl), a:
plot: ; druhá 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 ld (hl), a ; zápis hodnoty pixelu ret ; návrat z podprogramu
My ovšem musíme provést odlišnou operaci – vykreslení jediného pixelu, tj. zápis (resp. modifikaci) jediného bitu z celé osmice. Tuto operaci lze realizovat relativně snadno:
- Načtení původní hodnoty všech osmi bitů z obrazové paměti
- Aplikace vypočtené bitové masky operací or (ta v našem případě nastaví jediný bit)
- Uložení vypočtené nové hodnoty všech osmi bitů do obrazové paměti
Upravený kód bude vypadat takto:
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 ld d, (hl) ; přečíst původní hodnotu osmice pixelů or d ; použít vypočtenou masku pro nastavení jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu
Výsledky již mluví za vše:

Obrázek 17: Nyní se pixely správně překreslují přes pozadí.

Obrázek 18: Nyní se pixely správně překreslují přes pozadí.

Obrázek 19: Nyní se pixely správně překreslují přes pozadí.

Obrázek 20: Nyní se pixely správně překreslují přes pozadí.
17. Úplný zdrojový kód dnešního posledního demonstračního příkladu
Úplný zdrojový kód opraveného demonstračního příkladu s úpravou popsanou v předchozí kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/55-plot-pixel.asm a vypadá takto:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu 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 ld d, (hl) ; přečíst původní hodnotu osmice pixelů or d ; použít vypočtenou masku pro nastavení jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu 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 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 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 fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ret ; návrat z podprogramu 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 draw_char: ; Vytištění jednoho znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop2: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop2 ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Ani takto rozsáhlý příklad nebude po překladu do strojového kódu nijak závratně objemný, protože bude mít délku pouhých 143 bajtů:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:CD5880 CALL 8058 8003:0600 LD B, 00 8005:0E00 LD C, 00 8007: label loop 8007:CD2280 CALL 8022 800A:CD1680 CALL 8016 800D:04 INC B 800E:0C INC C 800F:78 LD A, B 8010:FEC0 CP C0 8012:20F3 JR NZ, 8007 8014: label finito 8014:18FE JR 8014 8016: label delay 8016:C5 PUSH BC 8017:0614 LD B, 14 8019: label outer_loop 8019:0E00 LD C, 00 801B: label inner_loop 801B:0D DEC C 801C:20FD JR NZ, 801B 801E:10F9 DJNZ 8019 8020:C1 POP BC 8021:C9 RET 8022: label plot 8022:CD3B80 CALL 803B 8025:CD2C80 CALL 802C 8028:56 LD D, (HL) 8029:B2 OR D 802A:77 LD (HL), A 802B:C9 RET 802C: label calc_pixel_value 802C:C5 PUSH BC 802D:78 LD A, B 802E:E607 AND 07 8030:47 LD B, A 8031:3E80 LD A, 80 8033:2804 JR Z, 8039 8035: label next_shift 8035:CB3F SRL A 8037:10FC DJNZ 8035 8039: label end_calc 8039:C1 POP BC 803A:C9 RET 803B: label calc_pixel_address 803B:79 LD A, C 803C:E607 AND 07 803E:F640 OR 40 8040:67 LD H, A 8041:79 LD A, C 8042:1F RRA 8043:1F RRA 8044:1F RRA 8045:E618 AND 18 8047:B4 OR H 8048:67 LD H, A 8049:79 LD A, C 804A:17 RLA 804B:17 RLA 804C:E6E0 AND E0 804E:6F LD L, A 804F:78 LD A, B 8050:1F RRA 8051:1F RRA 8052:1F RRA 8053:E61F AND 1F 8055:B5 OR L 8056:6F LD L, A 8057:C9 RET 8058: label fill_in_screen 8058:110040 LD DE, 4000 805B:CD6880 CALL 8068 805E:CD6880 CALL 8068 8061:CD6880 CALL 8068 8064:CD6880 CALL 8068 8067:C9 RET 8068: label draw_ascii_table 8068:3E20 LD A, 20 806A: label next_char 806A:F5 PUSH AF 806B:CD7A80 CALL 807A 806E:3E20 LD A, 20 8070:CD7A80 CALL 807A 8073:F1 POP AF 8074:3C INC A 8075:FE80 CP 80 8077:20F1 JR NZ, 806A 8079:C9 RET 807A: label draw_char 807A:01003C LD BC, 3C00 807D:61 LD H, C 807E:6F LD L, A 807F:29 ADD HL, HL 8080:29 ADD HL, HL 8081:29 ADD HL, HL 8082:09 ADD HL, BC 8083:0608 LD B, 08 8085:4A LD C, D 8086: label loop2 8086:7E LD A, (HL) 8087:12 LD (DE), A 8088:2C INC L 8089:14 INC D 808A:10FA DJNZ 8086 808C:1C INC E 808D:C8 RET Z 808E:51 LD D, C 808F:C9 RET 8090: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 808F
18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů
Výše uvedené demonstrační příklady i příklady, které již byly popsány v předchozích pěti článcích [1] [2], [3], [4], [5], je možné přeložit s využitím souboru Makefile, jehož aktuální verze vypadá následovně (pro překlad a slinkování je použit assembler Pasmo):
ASSEMBLER := pasmo all: 01.tap 02.tap 03.tap 04.tap 05.tap 06.tap 07.tap 08.tap 09.tap 10.tap \ 11.tap 12.tap 13.tap 14.tap 15.tap 16.tap 17.tap 18.tap 19.tap 20.tap \ 21.tap 22.tap 23.tap 24.tap 25.tap 26.tap 27.tap 28.tap 29.tap 30.tap \ 31.tap 32.tap 33.tap 34.tap 35.tap 36.tap 37.tap 38.tap 39.tap 40.tap \ 41.tap 42.tap 43.tap 44.tap 45.tap 46.tap 47.tap 48.tap 49.tap 50.tap \ 51.tap 52.tap 53.tap 54.tap 55.tap clean: rm -f *.tap .PHONY: all clean 01.tap: 01-color-attribute.asm $(ASSEMBLER) -v -d --tap $< $@ > 01-color-attribute.lst 02.tap: 02-blinking-attribute.asm $(ASSEMBLER) -v -d --tap $< $@ > 02-blinking-attribute.lst 03.tap: 03-symbolic-names.asm $(ASSEMBLER) -v -d --tap $< $@ > 03-symbolic-names.lst 04.tap: 04-operators.asm $(ASSEMBLER) -v -d --tap $< $@ > 04-operators.lst 05.tap: 05-better-symbols.asm $(ASSEMBLER) -v -d --tap $< $@ > 05-better-symbols.lst 06.tap: 06-tapbas-v1.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 06-tapbas-v1.lst 07.tap: 07-tapbas-v2.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 07-tapbas-v2.lst 08.tap: 08-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 08-loop.lst 09.tap: 09-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 09-loop.lst 10.tap: 10-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 10-loop.lst 11.tap: 11-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 11-loop.lst 12.tap: 12-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 12-loop.lst 13.tap: 13-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 13-loop.lst 14.tap: 14-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 14-loop.lst 15.tap: 15-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 15-loop.lst 16.tap: 16-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 16-loop.lst 17.tap: 17-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 17-loop.lst 18.tap: 18-cls.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 18-cls.lst 19.tap: 19-print-char-call.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 19-print-char-call.lst 20.tap: 20-print-char-rst.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 20-print-char-rst.lst 21.tap: 21-print-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 21-print-char.lst 22.tap: 22-print-all-chars.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 22-print-all-chars.lst 23.tap: 23-print-all-chars.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 23-print-all-chars.lst 24.tap: 24-change-color.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 24-change-color.lst 25.tap: 25-change-flash.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 25-change-flash.lst 26.tap: 26-print-at.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 26-print-at.lst 27.tap: 27-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 27-print-string.lst 28.tap: 28-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 28-print-string.lst 29.tap: 29-print-colorized-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 29-print-colorized-string.lst 30.tap: 30-print-string-ROM.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 30-print-string-ROM.lst 31.tap: 31-attributes.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 31-attributes.lst 32.tap: 32-fill-in-vram.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 32-fill-in-vram.lst 33.tap: 33-fill-in-vram-no-ret.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 33-fill-in-vram-no-ret.lst 34.tap: 34-fill-in-vram-pattern.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 34-fill-in-vram-pattern.lst 35.tap: 35-slow-fill-in-vram.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 35-slow-fill-in-vram.lst 36.tap: 36-slow-fill-in-vram-no-ret.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 36-slow-fill-in-vram-no-ret.lst 37.tap: 37-fill-block.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 37-fill-block.lst 38.tap: 38-fill-block-with-pattern.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 38-fill-block-with-pattern.lst 39.tap: 39-fill-block-optimized.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 39-fill-block-optimized.lst 40.tap: 40-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 40-draw-char.lst 41.tap: 41-draw-any-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 41-draw-any-char.lst 42.tap: 42-block-anywhere.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 42-block-anywhere.lst 43.tap: 43-block-anywhere-rrca.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 43-block-anywhere-rrca.lst 44.tap: 44-better-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 44-better-draw-char.lst 45.tap: 45-even-better-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 45-even-better-draw-char.lst 46.tap: 46-draw-char-at.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 46-draw-char-at.lst 47.tap: 47-draw-char-at-unrolled.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 47-draw-char-at-unrolled.lst 48.tap: 48-incorrect-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 48-incorrect-print-string.lst 49.tap: 49-correct-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 49-correct-print-string.lst 50.tap: 50-ascii-table.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 50-ascii-table.lst 51.tap: 51-plot-block.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 51-plot-block.lst 52.tap: 52-plot-pixel.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 52-plot-pixel.lst 53.tap: 53-plot-pixel.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 53-plot-pixel.lst 54.tap: 54-plot-pixel-on-background.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 54-plot-pixel-on-background.lst 55.tap: 55-plot-pixel-on-background.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 55-plot-pixel-on-background.lst
19. Repositář s demonstračními příklady
V tabulce zobrazené pod tímto odstavcem jsou uvedeny odkazy na všechny prozatím popsané demonstrační příklady určené pro překlad a spuštění na osmibitovém domácím mikropočítači ZX Spectrum (libovolný model či jeho klon), které jsou psány v assembleru mikroprocesoru Zilog Z80. Pro překlad těchto demonstračních příkladů je možné použít například assembler Pasmo (viz též úvodní článek):
# | Soubor | Stručný popis | Adresa |
---|---|---|---|
1 | 01-color-attribute.asm | modifikace jednoho barvového atributu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/01-color-attribute.asm |
2 | 02-blinking-attribute.asm | barvový atribut s nastavením bitů pro blikání a vyšší intenzitu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/02-blinking-attribute.asm |
3 | 03-symbolic-names.asm | symbolická jména v assembleru | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/03-symbolic-names.asm |
4 | 04-operators.asm | operátory a operace se symbolickými hodnotami | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/04-operators.asm |
5 | 05-better-symbols.asm | tradičnější symbolická jména | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/05-better-symbols.asm |
6 | 06-tapbas-v1.asm | vygenerování BASICovského loaderu (neúplný příklad) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/06-tapbas-v1.asm |
7 | 07-tapbas-v2.asm | vygenerování BASICovského loaderu (úplný příklad) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/07-tapbas-v2.asm |
8 | 08-loop.asm | jednoduchá počítaná programová smyčka: naivní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/08-loop.asm |
9 | 09-loop.asm | programová smyčka: zkrácení kódu pro vynulování použitých pracovních registrů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/09-loop.asm |
10 | 10-loop.asm | programová smyčka: optimalizace skoku na konci smyčky (instrukce DJNZ) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/10-loop.asm |
11 | 11-loop.asm | programová smyčka: optimalizace využití pracovních registrů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/11-loop.asm |
12 | 12-loop.asm | programová smyčka: použití pracovního registru IX | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/12-loop.asm |
13 | 13-loop.asm | programová smyčka: použití pracovního registru IY | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/13-loop.asm |
14 | 14-loop.asm | programová smyčka se šestnáctibitovým počitadlem, základní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/14-loop.asm |
15 | 15-loop.asm | programová smyčka se šestnáctibitovým počitadlem, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/15-loop.asm |
16 | 16-loop.asm | použití relativního skoku a nikoli skoku absolutního | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/16-loop.asm |
17 | 17-loop.asm | programová smyčka: inc l namísto inc hl | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/17-loop.asm |
18 | 18-cls.asm | smazání obrazovky a otevření kanálu číslo 2 (screen) přes funkci v ROM | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/18-cls.asm |
19 | 19-print-char-call.asm | smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce CALL) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/19-print-char-call.asm |
20 | 20-print-char-rst.asm | smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce RST) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/20-print-char-rst.asm |
21 | 21-print-char.asm | pouze výpis jednoho znaku na obrazovku bez jejího smazání | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/21-print-char.asm |
22 | 22-print-all-chars.asm | výpis znakové sady znak po znaku (nekorektní verze příkladu) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/22-print-all-chars.asm |
23 | 23-print-all-chars.asm | výpis znakové sady znak po znaku (korektní verze příkladu) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/23-print-all-chars.asm |
24 | 24-change-color.asm | změna barvových atributů (popředí a pozadí) vypisovaných znaků | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/24-change-color.asm |
25 | 25-change-flash.asm | povolení či zákaz blikání vypisovaných znaků | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/25-change-flash.asm |
26 | 26-print-at.asm | výpis znaku či znaků na určené místo na obrazovce | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/26-print-at.asm |
27 | 27-print-string.asm | výpis celého řetězce explicitně zapsanou programovou smyčkou (základní varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/27-print-string.asm |
28 | 28-print-string.asm | výpis celého řetězce explicitně zapsanou programovou smyčkou (vylepšená varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/28-print-string.asm |
29 | 29-print-colorized-string.asm | výpis řetězce, který obsahuje i řídicí znaky pro změnu barvy atd. | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/29-print-colorized-string.asm |
30 | 30-print-string-ROM.asm | výpis řetězce s využitím služby/subrutiny uložené v ROM ZX Spectra | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/30-print-string-ROM.asm |
31 | 31-attributes.asm | modifikace atributů pro tisk řetězce subrutinou uloženou v ROM | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/31-attributes.asm |
32 | 32-fill-in-vram.asm | vyplnění celé bitmapy barvou popředí, návrat do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/32-fill-in-vram.asm |
33 | 33-fill-in-vram-no-ret.asm | vyplnění celé bitmapy barvou popředí, bez návratu do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/33-fill-in-vram-no-ret.asm |
34 | 34-fill-in-vram-pattern.asm | vyplnění celé bitmapy zvoleným vzorkem | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/34-fill-in-vram-pattern.asm |
35 | 35-slow-fill-in-vram.asm | pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/35-slow-fill-in-vram.asm |
36 | 36-slow-fill-in-vram-no-ret.asm | pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy, bez návratu do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/36-slow-fill-in-vram-no-ret.asm |
37 | 37-fill-block.asm | vykreslení bloku 8×8 pixelů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/37-fill-block.asm |
38 | 38-fill-block-with-pattern.asm | vykreslení bloku 8×8 pixelů zvoleným vzorkem | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/38-fill-block-with-pattern.asm |
39 | 39-fill-block-optimized.asm | optimalizace předchozího příkladu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/39-fill-block-optimized.asm |
40 | 40-draw-char.asm | vykreslení znaku do levého horního rohu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/40-draw-char.asm |
41 | 41-draw-any-char.asm | podprogram pro vykreslení libovolně zvoleného znaku do levého horního rohu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/41-draw-any-char.asm |
42 | 42-block-anywhere.asm | podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/42-block-anywhere.asm |
43 | 43-block-anywhere-rrca.asm | podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/43-block-anywhere-rrca.asm |
44 | 44-better-draw-char.asm | vykreslení znaku v masce 8×8 pixelů, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/44-better-draw-char.asm |
45 | 45-even-better-draw-char.asm | posun offsetu pro vykreslení dalšího znaku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/45-even-better-draw-char.asm |
46 | 46-draw-char-at.asm | vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/46-draw-char-at.asm |
47 | 47-draw-char-at-unrolled.asm | vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/47-draw-char-at-unrolled.asm |
48 | 48-incorrect-print-string.asm | tisk řetězce, nekorektní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/48-incorrect-print-string.asm |
49 | 49-correct-print-string.asm | tisk řetězce, korektní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/49-correct-print-string.asm |
50 | 50-ascii-table.asm | tisk několika bloků ASCII tabulky | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/50-ascii-table.asm |
51 | 51-plot-block.asm | vykreslení pixelu verze 1: zápis celého bajtu na pozici pixelu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/51-plot-block.asm |
52 | 52-plot-pixel.asm | vykreslení pixelu verze 2: korektní vykreslení jednoho pixelu, ovšem překreslení celého bajtu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/52-plot-pixel.asm |
53 | 53-plot-pixel.asm | vykreslení pixelu verze 3: vylepšená verze předchozího demonstračního příkladu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/53-plot-pixel.asm |
54 | 54-plot-pixel-on-background.asm | vykreslení pixelu vůči pozadí (nekorektní varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/54-plot-pixel-on-background.asm |
55 | 55-plot-pixel-on-background.asm | vykreslení pixelu vůči pozadí (korektní varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/55-plot-pixel-on-background.asm |
56 | Makefile | Makefile pro překlad a slinkování všech demonstračních příkladů do podoby obrazu magnetické pásky | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/Makefile |
20. Odkazy na Internetu
- z80 standalone assembler
https://www.asm80.com/onepage/asmz80.html - The ZX BASIC Compiler
https://www.boriel.com/pages/the-zx-basic-compiler.html - Z80 Assembly programming for the ZX Spectrum
https://www.chibiakumas.com/z80/ZXSpectrum.php - 8-BIT SMACKDOWN! 65C02 vs. Z80: slithy VLOGS #6
https://www.youtube.com/watch?v=P1paVoFEvyc - Instrukce mikroprocesoru Z80
https://clrhome.org/table/ - Z80 instructions: adresní režimy atd.
https://jnz.dk/z80/instructions.html - Z80 Instruction Groups
https://jnz.dk/z80/instgroups.html - Elena, New programming language for the ZX Spectrum Next
https://vintageisthenewold.com/elena-new-programming-language-for-the-zx-spectrum-next/ - Sinclair BASIC
https://worldofspectrum.net/legacy-info/sinclair-basic/ - Grafika na osmibitových počítačích firmy Sinclair
https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair/ - Grafika na osmibitových počítačích firmy Sinclair II
https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/ - HiSoft BASIC
https://worldofspectrum.net/infoseekid.cgi?id=0008249 - YS MegaBasic
https://worldofspectrum.net/infoseekid.cgi?id=0008997 - Beta Basic
https://worldofspectrum.net/infoseekid.cgi?id=0007956 - BASIC+
https://worldofspectrum.net/infoseekid.php?id=0014277 - Spectrum ROM Memory Map
https://skoolkit.ca/disassemblies/rom/maps/all.html - Goto subroutine
https://skoolkit.ca/disassemblies/rom/asm/7783.html - Spectrum Next: The Evolution of the Speccy
https://www.specnext.com/about/ - Sedmdesátiny assemblerů: lidsky čitelný strojový kód
https://www.root.cz/clanky/sedmdesatiny-assembleru-lidsky-citelny-strojovy-kod/ - Programovací jazyk BASIC na osmibitových mikropočítačích
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich/ - Programovací jazyk BASIC na osmibitových mikropočítačích (2)
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich-2/#k06 - Programovací jazyk BASIC na osmibitových mikropočítačích (3)
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich-3/ - Sinclair BASIC (Wikipedia CZ)
http://cs.wikipedia.org/wiki/Sinclair_BASIC - Assembly Language: Still Relevant Today
http://wilsonminesco.com/AssyDefense/ - Programovani v assembleru na OS Linux
http://www.cs.vsb.cz/grygarek/asm/asmlinux.html - Why Assembly Language Programming? (Why Learning Assembly Language Is Still a Good Idea)
https://wdc65×x.com/markets/education/why-assembly-language-programming/ - Low Fat Computing
http://www.ultratechnology.com/lowfat.htm - Assembly Language
https://www.cleverism.com/skills-and-tools/assembly-language/ - Why do we need assembly language?
https://cs.stackexchange.com/questions/13287/why-do-we-need-assembly-language - Assembly language (Wikipedia)
https://en.wikipedia.org/wiki/Assembly_language#Historical_perspective - Assembly languages
https://curlie.org/Computers/Programming/Languages/Assembly/ - vasm
http://sun.hasenbraten.de/vasm/ - B-ELITE
https://jsj.itch.io/b-elite - ZX-Spectrum Child
http://www.dotkam.com/2008/11/19/zx-spectrum-child/ - Speccy.cz
http://www.speccy.cz/ - Planet Sinclair
http://www.nvg.ntnu.no/sinclair/ - World of Spectrum
http://www.worldofspectrum.org/ - The system variables
https://worldofspectrum.org/ZXBasicManual/zxmanchap25.html - ZX Spectrum manual: chapter #17 Graphics
https://worldofspectrum.org/ZXBasicManual/zxmanchap17.html