Obsah
1. Vývoj her a grafických i zvukových dem pro ZX Spectrum: vlastní vykreslovací subrutiny
2. Modifikace barvy a dalších atributů vykreslovaného řetězce
3. Struktura bitmapy tvořící část obrazové paměti
4. Vyplnění celé bitmapy pixely s barvou inkoustu
5. Program bez návratu do BASICu
6. Vyplnění obrazové paměti vybraným vzorkem
7. Zpomalené vyplňování obrazové paměti: vizualizace její struktury
8. Vlastní vykreslovací subrutiny – od vykreslení bloku k vykreslení celého (obarveného) řetězce
9. Vykreslení vzorku 8×8 pixelů: základ pro algoritmus vykreslení znaků a řetězců
10. Vykreslení vertikálního vzorku namísto vyplněného bloku
11. Optimalizace vykreslení bloku: využití specifické struktury obrazové paměti ZX Spectra
12. Vykreslení znaku s využitím masky uložené v paměti ROM
13. Úplný zdrojový kód příkladu pro vykreslení znaku
14. Podprogram pro vykreslení libovolně zvoleného znaku na obrazovku
15. Výpočet adresy v obrazové paměti pro vykreslení bloku
16. Vykreslení bloku 8×8 na libovolné místo na obrazovce
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
V pořadí již čtvrté části seriálu o vývoji programů pro legendární osmibitový domácí mikropočítač ZX Spectrum si ukážeme různé způsoby přímé manipulace s obrazovou pamětí, tedy konkrétně takové postupy, které se obejdou bez využití podprogramů, které jsou uloženy v paměti ROM. Popíšeme si tedy jak strukturu obrazové paměti (prozatím jsme si v demonstračních příkladech otestovali „pouze“ strukturu paměti s barvovými atributy, nikoli ovšem způsob uložení podkladové bitmapy), tak i některé vlastní rutiny použitelné pro vykreslování různých obrazců, ale i běžného textu. Později se budeme věnovat i některým postupům, které se na ZX Spectru používají pro vykreslení pohybujících se objektů (spritů). Nakonec se zmíníme o problematice práce s okrajem obrazovky (border), protože i tato část obrazu může být do jisté míry využita (buď například pro rozšíření obzoru nebo alespoň ve fázi ladění pro odhad doby trvání některých operací).
Obrázek 1: Vícebarevný a částečně blikající text vytištěný s využitím vykreslovací rutiny dostupné ve standardní paměti ROM. Způsob využití této rutiny (subrutiny) jsme si popsali minule.
V ROM jsou uloženy i pokročilejší vykreslovací subrutiny, jejichž funkcionalita odpovídá příslušným příkazům Sinclair BASICu. Konkrétně se jedná o příkazy PLOT, DRAW, CIRCLE pro vykreslení obrazců, které jsou doplněné o nastavovací příkazy INK, PAPER, FLASH, BRIGHT, INVERSE, OVER, BORDER a o příkaz POINT sloužící pro zjištění stavu obrazové paměti. Tyto příkazy, resp. jejich subrutiny, je možné volat i z assembleru, ale vzhledem k formátu a struktuře parametrů bude výhodnější použít odlišné postupy.
Obrázek 2: Vícebarevný a částečně blikající text vytištěný s využitím vykreslovací rutiny dostupné ve standardní paměti ROM.
2. Modifikace barvy a dalších atributů vykreslovaného řetězce
Ve třetí části tohoto seriálu jsme si ukázali, jakým způsobem je možné využít subrutinu nazvanou ROM_PRINT, která je uložena od adresy 0×203C. Tato subrutina umožňuje vykreslení celého řetězce na obrazovku, přičemž v řetězci mohou být uloženy i řídicí znaky pro změnu barvy i dalších atributů vykreslovaných znaků. Poslední nastavená barva a atributy (konkrétně blikání, vysoká intenzita barvy inkoustu) jsou touto subrutinou uloženy do bajtu umístěného na adrese 23695 (což je RAM, protože se jedná o adresu vyšší než 16383). V případě, že hodnotu uloženou na této adrese (její symbolické jméno je ATTR_T) přímo změníme, je možné modifikovat barvy následně vykreslovaného řetězce či řetězců (zapsaná hodnota je zapamatována do té doby, dokud ji přímo či nepřímo nezměníme buď my nebo systém/interpret).
V následujícím příkladu je ukázána modifikace bajtu na adrese ATTR_T následovaná vykreslením řetězce rutinou z adresy ROM_PRINT. Jak změna barvy, tak i vykreslení řetězce probíhá ve smyčce:
ENTRY_POINT equ $8000
ROM_OPEN_CHANNEL equ $1601
ROM_PRINT equ $203C
ATTR_T equ 23695
org ENTRY_POINT
start:
ld A,2 ; číslo kanálu
call ROM_OPEN_CHANNEL ; otevření kanálu číslo 2 (screen)
ld B, 64 ; barva tisku
ld HL, ATTR_T ; adresa systémové proměnné ATTR_T
loop:
ld (HL), B ; změna barvy tisku
push BC ; uchovat BC
ld DE, TEXT ; adresa prvního znaku v řetězci
ld BC, TEXT_LENGTH ; délka textu
call ROM_PRINT ; volání subrutiny v ROM
pop BC ; obnovit BC
djnz loop ; tisk další barvou
ret ; ukončit program
; řetězec
TEXT: db "Hello, speccy!"
TEXT_LENGTH: equ $ - TEXT
end ENTRY_POINT
Takto vypadá výsledek:
Obrázek 3: Obrazovka vyplněná barevným textem vykresleným příkladem (ROM rutina si navíc vynutí pokračování vykreslování po stisku klávesy).
Obrázek 4: Poslední obarvené zprávy vypsané demonstračním příkladem.
Způsob překladu do strojového kódu vypadá následovně (samotná textová zpráva je uložena ihned za strojovým kódem):
ENTRY_POINT EQU 8000
ROM_OPEN_CHANNEL EQU 1601
ROM_PRINT EQU 203C
ATTR_T EQU 5C8F
ORG 8000
8000: label start
8000:3E02 LD A, 02
8002:CD0116 CALL 1601
8005:0640 LD B, 40
8007:218F5C LD HL, 5C8F
800A: label loop
800A:70 LD (HL), B
800B:C5 PUSH BC
800C:111980 LD DE, 8019
800F:010E00 LD BC, 000E
8012:CD3C20 CALL 203C
8015:C1 POP BC
8016:10F2 DJNZ 800A
8018:C9 RET
8019: label TEXT
8019:48656C6C DEFB of 14 bytes
801D:6F2C2073
8021:70656363
8025:7921
TEXT_LENGTH EQU 000E
8027: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8026
3. Struktura bitmapy tvořící část obrazové paměti
Z pohledu mikroprocesoru (tj. jeho adresové sběrnice) začínala obrazová paměť na adrese 16384, protože do prvních 16 kB byla namapována paměť ROM a od adresy 32768 se nacházel blok paměti RAM o kapacitě 32 kB, do které měl přístup pouze mikroprocesor. Jediný blok paměti, který byl sdílený mezi ULA (to je čip, který se kromě dalších operací staral o generování video signálu) a mikroprocesorem, se nacházel od zmíněné adresy 16384 až do adresy 32767.
Obrázek 5: Některé hry dokážou vykreslit i obrovské animované sprity (Trap Door a jeho pokračování).
Obrazová paměť je rozdělena na dvě části – bitmapu s rozlišením 256×192 pixelů o velikosti 256×192/8=6144 bajtů a maticí atributů s 32 24=768 bajty (práci s touto částí obrazové paměti již známe). Každému pixelu je v bitmapě přiřazen jediný bit rozlišující, zda se jedná o pixel s barvou pozadí (paper) či popředí (ink), což znamená, že v jednom bajtu jsou uloženy informace o osmi pixelech ležících na jednom obrazovém řádku. Pixely na jednom obrazovém řádku, tvořící blok o délce 32 bajtů, jsou uloženy lineárně za sebou, ovšem jednotlivé obrazové řádky jsou v paměti uloženy na přeskáčku: šestnáctibitovou adresu lze v případě práce s obrazovou pamětí rozdělit na více částí tak, jak je naznačeno níže (každé písmeno odpovídá jednomu bitu šestnáctibitové adresy):
010 BB SSS RRR CCCCC BS: čí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ů
Obrázek 6: Statický obrázek umně využívající omezení atributové paměti.
Nejlépe je tato organizace paměti patrná při nahrávání programu z kazety, neboť nahrávací rutina pouze načítá data a postupně je ukládá od adresy 16384, tj. na obrazovce se nahrávaná bitmapa objevuje po jednotlivých „zpřeházených“ řádcích: nejprve první řádek, řádek osmý, šestnáctý atd. až dojde k vyplnění celého bloku o výšce 64 obrazových řádků. Viz například toto video.
Obrázek 7: Disassembler, v němž se používají především subrutiny pro vykreslení textu.
4. Vyplnění celé bitmapy pixely s barvou inkoustu
Vzhledem k tomu, že bitmapa je sice v obrazové paměti uložena jako sekvence „zpřeházených“ řádků, ale mezi obrazovými řádky nejsou v paměti žádné mezery, je možné celou bitmapu jednoduše vyplnit bitovým vzorkem popř. konstantní barvou (popředí či pozadí). To je operace, kterou realizujeme programovou smyčkou, jejíž základy jsme si popsali minule – vnitřní část smyčky vždy vyplní 256 bajtový blok, vnější smyčka tuto operaci zopakuje 24×, takže se celkově vyplní 256×24=6144 bajtů, což je přesná velikost bitmapy počítaná v bajtech (pixelů je pochopitelně osmkrát více):
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld a, PIXELS/8/256 ; počet opakování bloku s 256 zápisy
ld b, 0 ; počitadlo vnitřní smyčky
loop:
ld (hl), 0xff ; zápis hodnoty na adresu (HL)
inc hl ; zvýšení adresy
djnz loop ; vnitřní smyčka: blok s 256 zápisy
dec a ; počitadlo vnější smyčky
jp NZ, loop ; skok pokud se ještě nedosáhlo nuly
finish:
ret ; ukončit program
end ENTRY_POINT
Výsledek by měl vypadat následovně (povšimněte si, že část bitmapy byla přepsána interpretrem BASICu po ukončení programu):
Obrázek 8: Bitmapa vyplněná barvou popředí (ink) a částečně přepsaná interpretem BASICu.o
Překlad výše uvedené smyčky do strojového kódu může vypadat takto:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:3E18 LD A, 18
8005:0600 LD B, 00
8007: label loop
8007:36FF LD (HL), FF
8009:23 INC HL
800A:10FB DJNZ 8007
800C:3D DEC A
800D:C20780 JP NZ, 8007
8010: label finish
8010:C9 RET
8011: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8010
5. Program bez návratu do BASICu
Program z předchozí kapitoly končil instrukcí ret, což znamená, že se řízení vrátilo zpět do BASICu, který část obrázku překreslil vlastními zprávami:
finish:
ret ; ukončit program
Pokud namísto ret použijeme nekonečnou smyčku, program v této smyčce pochopitelně setrvá a zprávy BASICu se již neobjeví:
finish:
jr finish ; nevrátíme se do systému
Výsledkem by měla být bitmapa, která je celá vyplněná samými jedničkami (tedy pixely s barvou popředí):
Obrázek 9: Bitmapa vyplněná barvou popředí (ink) (nyní již BASIC žádnou část obrazovky nepřekreslil).
Pro úplnost si ukažme celý zdrojový kód takto upraveného příkladu:
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld a, PIXELS/8/256 ; počet opakování bloku s 256 zápisy
ld b, 0 ; počitadlo vnitřní smyčky
loop:
ld (hl), 0xff ; zápis hodnoty na adresu (HL)
inc hl ; zvýšení adresy
djnz loop ; vnitřní smyčka: blok s 256 zápisy
dec a ; počitadlo vnější smyčky
jp NZ, loop ; skok pokud se ještě nedosáhlo nuly
finish:
jr finish ; nevrátíme se do systému
end ENTRY_POINT
Způsob překladu do strojového kódu ukazuje, že instrukce jr je pouze dvoubajtová, na rozdíl od absolutního skoku:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:3E18 LD A, 18
8005:0600 LD B, 00
8007: label loop
8007:36FF LD (HL), FF
8009:23 INC HL
800A:10FB DJNZ 8007
800C:3D DEC A
800D:C20780 JP NZ, 8007
8010: label finish
8010:18FE JR 8010
8012: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8011
6. Vyplnění obrazové paměti vybraným vzorkem
Samozřejmě není nutné obrazovou paměť vyplnit pouze samými bitovými jedničkami (tedy bajty s hodnotou 0×ff v případě, že se provádí zápis po bajtech a nikoli po slovech). Pokud pro zápis použijeme odlišnou zapisovanou hodnotu, například 10101010 (bitově), dojde podle očekávání k vizuální změně na obrazovce, která by nyní měla obsahovat vertikální úsečky s šířkou jednoho pixelu:
Obrázek 10: Bitmapa s pixely vyplněnými barvou popředí (ink) a barvou pozadí (paper).
Celý programový kód je upraven jen nepatrně – použijeme konstantu PATTERN se zapisovaným bitovým vzorkem:
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
PATTERN equ %10101010
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld a, PIXELS/8/256 ; počet opakování bloku s 256 zápisy
ld b, 0 ; počitadlo vnitřní smyčky
loop:
ld (hl), PATTERN ; zápis hodnoty na adresu (HL)
inc hl ; zvýšení adresy
djnz loop ; vnitřní smyčka: blok s 256 zápisy
dec a ; počitadlo vnější smyčky
jp NZ, loop ; skok pokud se ještě nedosáhlo nuly
finish:
jr finish ; nevrátíme se do systému
end ENTRY_POINT
Pro úplnost si opět uveďme způsob překladu tohoto demonstračního příkladu do strojového kódu:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
PATTERN EQU 00AA
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:3E18 LD A, 18
8005:0600 LD B, 00
8007: label loop
8007:36AA LD (HL), AA
8009:23 INC HL
800A:10FB DJNZ 8007
800C:3D DEC A
800D:C20780 JP NZ, 8007
8010: label finish
8010:18FE JR 8010
8012: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8011
7. Zpomalené vyplňování obrazové paměti: vizualizace její struktury
V úvodním textu jsme si řekli, že struktura obrazové paměti je dobře viditelná při nahrávání obrázků z kazety. Ovšem pokud zpomalíme naši vykreslovací smyčku, bude struktura paměti patrná i při spuštění takto upraveného programu – celé vyplňování může probíhat tak pomalu, aby bylo viditelné lidským okem. To je ukázáno v dalším demonstračním příkladu, v němž před vykreslením každé osmice pixelů 256× snižujeme obsah akumulátoru a teprve poté se vykreslení provede (to nám mimochodem ukazuje, jak je vlastně Z80 relativně pomalý a jak málo strojového času mají k dispozici běžné vykreslovací subrutiny, které překreslují celou obrazovku – což jsou hry se scrollujícím světem). Samotná zpožďovací smyčka je realizována třemi instrukcemi okolo návěští delay:
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld c, PIXELS/8/256 ; počet opakování bloku s 256 zápisy
ld b, 0 ; počitadlo vnitřní smyčky
loop:
xor a ; počitadlo zpožďovací smyčky
delay:
dec a ; snížení hodnoty počitadla (v první iteraci 256->255)
jr NZ, delay ; opakovat, dokud není dosaženo nuly
ld (hl), 0xff ; zápis hodnoty na adresu (HL)
inc hl ; zvýšení adresy
djnz loop ; vnitřní smyčka: blok s 256 zápisy
dec c ; počitadlo vnější smyčky
jp NZ, loop ; skok pokud se ještě nedosáhlo nuly
ret ; návrat do systému
end ENTRY_POINT
Obrázek 11: Postupné vyplňování obrazové paměti pixely s barvou inkoustu.
Obrázek 12: Postupné vyplňování obrazové paměti pixely s barvou inkoustu.
Obrázek 13: Postupné vyplňování obrazové paměti pixely s barvou inkoustu.
Obrázek 14: Postupné vyplňování obrazové paměti pixely s barvou inkoustu.
Překlad do strojového kódu bude vypadat takto:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:0E18 LD C, 18
8005:0600 LD B, 00
8007: label loop
8007:AF XOR A
8008: label delay
8008:3D DEC A
8009:20FD JR NZ, 8008
800B:36FF LD (HL), FF
800D:23 INC HL
800E:10F7 DJNZ 8007
8010:0D DEC C
8011:C20780 JP NZ, 8007
8014:C9 RET
8015: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8014
Pro úplnost si uveďme upravenou variantu tohoto příkladu, který nikdy nepředá řízení zpět do BASICu a tudíž se výsledná obrazovka nepřekreslí:
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld c, PIXELS/8/256 ; počet opakování bloku s 256 zápisy
ld b, 0 ; počitadlo vnitřní smyčky
loop:
xor a ; počitadlo zpožďovací smyčky
delay:
dec a ; snížení hodnoty počitadla (v první iteraci 256->255)
jr NZ, delay ; opakovat, dokud není dosaženo nuly
ld (hl), 0xff ; zápis hodnoty na adresu (HL)
inc hl ; zvýšení adresy
djnz loop ; vnitřní smyčka: blok s 256 zápisy
dec c ; počitadlo vnější smyčky
jp NZ, loop ; skok pokud se ještě nedosáhlo nuly
finish:
jr finish ; nevrátíme se do systému
end ENTRY_POINT
Struktura instrukcí ve strojovém kódu vypadá takto:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:0E18 LD C, 18
8005:0600 LD B, 00
8007: label loop
8007:AF XOR A
8008: label delay
8008:3D DEC A
8009:20FD JR NZ, 8008
800B:36FF LD (HL), FF
800D:23 INC HL
800E:10F7 DJNZ 8007
8010:0D DEC C
8011:C20780 JP NZ, 8007
8014: label finish
8014:18FE JR 8014
8016: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8015
8. Vlastní vykreslovací subrutiny – od vykreslení bloku k vykreslení celého (obarveného) řetězce
Koncepty představené v předchozích kapitolách jsou sice prozatím velmi jednoduché, ale postupně je můžeme a budeme rozšiřovat takovým způsobem, abychom (minimálně) dokázali na obrazovku vykreslit znak či celý řetězec znaků, a to bez volání subrutin uložených v paměti ROM. A samozřejmě nám nic nebude bránit ve vykreslení obarveného řetězce (zvláště v situaci, kdy budeme vykreslovat zarovnané znaky s maskou o velikosti 8×8 pixelů). Samotné masky znaků bude přitom možné číst jak z paměti ROM (což znamená standardní znakovou sadu odvozenou od ASCII) nebo z paměti RAM, což nám naopak umožní využití vlastní znakové sady doplněné například o znaky s nabodeníčky atd. A nesmíme zapomenout na to, že způsob vykreslování spritů se v mnoha ohledech podobá vykreslování znaků, takže se nabízí možnost vytvoření univerzálnějších algoritmů (které však nebudou tak rychlé, jako algoritmy plně optimalizované na provedení jediné grafické operace).
9. Vykreslení vzorku 8×8 pixelů: základ pro algoritmus vykreslení znaků a řetězců
Podprogram pro tisk celého řetězce je poměrně komplikovaný. Z tohoto důvodu budeme postupovat po jednotlivých krocích. Nejdříve si vyzkoušíme, jak se vykreslí blok o velikosti 8×8 pixelů do levého horního rohu obrazovky. Postup je snadný:
- Nastavíme pracovní adresu (pro zápis) na hodnotu 0×4000, což je první bajt obrazové paměti
- Nastavíme počitadlo smyčky na hodnotu 8, protože se má vykreslit osm obrazových řádků
- Na vypočtenou adresu se zapíše hodnota 0×ff. Tím se vykreslí osm horizontálních pixelů
- Adresa se zvýší o hodnotu odpovídající délce obrazového řádku v bajtech vynásobené osmi (protože řádky nejsou uloženy za sebou)
- Sníží se počitadlo smyčky o jedničku
- Pokud se nedosáhlo nuly, bude program pokračovat od bodu 3
Tento postup je sice poměrně neoptimální (zejména výpočet adresy lze upravit, jak ostatně uvidíme dále), ale zato je relativně snadno pochopitelný. Samotná vykreslovací smyčka je tvořena pouze třemi instrukcemi:
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
SCANLINE_LENGTH equ 256/8
NEXT_SCANLINE equ SCANLINE_LENGTH*8
PATTERN equ $ff
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld b, 8 ; počitadlo zapsaných bajtů
ld de, NEXT_SCANLINE ; offset pro přechod na další obrazový řádek
loop:
ld (hl), PATTERN ; zápis hodnoty na adresu (HL)
add hl, de ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
finish:
ret ; ukončit program
end ENTRY_POINT
Obrázek 15: Blok o velikosti 8×8 pixelů vykreslený do levého horního rohu obrazovky (to prosím není kurzor :-).
Překlad do strojového kódu dopadne následovně:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
SCANLINE_LENGTH EQU 0020
NEXT_SCANLINE EQU 0100
PATTERN EQU 00FF
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:0608 LD B, 08
8005:110001 LD DE, 0100
8008: label loop
8008:36FF LD (HL), FF
800A:19 ADD HL, DE
800B:10FB DJNZ 8008
800D: label finish
800D:C9 RET
800E: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 800D
10. Vykreslení vertikálního vzorku namísto vyplněného bloku
Podobně jako jsme vyplnili obrazovku jak barvou inkoustu (zapisované bajty měly hodnotu 0×ff) tak i zvoleným vzorkem, můžeme totéž udělat i v případě vykreslení bloku 8×8 pixelů. I zde lze původní zapisovanou hodnotu 0×ff nahradit jinou hodnotou, například bitovým vzorkem 10101010. Výsledek by měl vypadat následovně:
Obrázek 16: Blok o velikosti 8×8 pixelů vyplněný vzorkem a vykreslený do levého horního rohu obrazovky.
Samotný programový kód se přitom změní jen nepatrně – odlišnou hodnotou uloženou do symbolické hodnoty PATTERN:
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
SCANLINE_LENGTH equ 256/8
NEXT_SCANLINE equ SCANLINE_LENGTH*8
PATTERN equ %10101010
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld b, 8 ; počitadlo zapsaných bajtů
ld de, NEXT_SCANLINE ; offset pro přechod na další obrazový řádek
loop:
ld (hl), PATTERN ; zápis hodnoty na adresu (HL)
add hl, de ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
finish:
ret ; ukončit program
end ENTRY_POINT
Výsledek překladu zdrojového kódu z assembleru do strojového kódu:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
SCANLINE_LENGTH EQU 0020
NEXT_SCANLINE EQU 0100
PATTERN EQU 00AA
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:0608 LD B, 08
8005:110001 LD DE, 0100
8008: label loop
8008:36AA LD (HL), AA
800A:19 ADD HL, DE
800B:10FB DJNZ 8008
800D: label finish
800D:C9 RET
800E: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 800D
11. Optimalizace vykreslení bloku: využití specifické struktury obrazové paměti ZX Spectra
Podívejme se ještě jednou na strukturu adresy bajtu zapisovaného do obrazové paměti:
010 BB SSS RRR CCCCC BS: nyní nezajímavé SSS: číslo řádky v jednom znaku, který je vysoký osm obrazových řádků RRR: nyní nezajímavé CCCCC: nyní nezajímavé
Z poněkud upraveného popisku je patrné, že v dolních třech bitech vyššího bajtu adresy je zapsáno číslo obrazového řádku 0..7. To vlastně znamená, že pokud vypisujeme znak s maskou 8×8 pixelů, je zápis dalšího bajtu proveden na adresu o 256 vyšší, než byla adresa předchozí. A vzhledem k tomu, že adresa je zapsána ve dvojici registrů HL (či BC nebo DE), postačuje nám pouze zvýšit hodnotu v horním osmibitovém registru – což je mnohem rychlejší i kratší operace!
loop:
ld (hl), PATTERN ; zápis hodnoty na adresu (HL)
inc h ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
Obrázek 17: Blok o velikosti 8×8 pixelů vyplněný vzorkem a vykreslený do levého horního rohu obrazovky.
Takto vypadá upravený zdrojový kód, který bude po překladu kratší a současně i rychlejší:
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PIXELS equ 256*192
PATTERN equ %10101010
org ENTRY_POINT
start:
ld hl, SCREEN_ADR ; adresa pro zápis
ld b, 8 ; počitadlo zapsaných bajtů
loop:
ld (hl), PATTERN ; zápis hodnoty na adresu (HL)
inc h ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
finish:
ret ; ukončit program
end ENTRY_POINT
Povšimněte si, že kód je kratší o 0×800d-0×800a=3 bajty. Navíc instrukce inc h trvá čtyři takty, zatímco add hl, de trvá taktů jedenáct:
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PIXELS EQU C000
PATTERN EQU 00AA
ORG 8000
8000: label start
8000:210040 LD HL, 4000
8003:0608 LD B, 08
8005: label loop
8005:36AA LD (HL), AA
8007:24 INC H
8008:10FB DJNZ 8005
800A: label finish
800A:C9 RET
800B: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 800A
12. Vykreslení znaku s využitím masky uložené v paměti ROM
Nyní si již konečně ukažme, jak se vlastně vykreslí skutečný znak, například znak „A“. Masky znaků jsou uloženy v paměti ROM od adresy 0×3c00, takže předchozí vykreslovací rutinu musíme nepatrně změnit – namísto konstantního vzorku budeme muset 8× načíst bajt z ROM a samozřejmě vždy zvýšit adresu o jedničku pro další čtení. Pro čtení znaků z ROM je použit registrový pár DE, pro zápis do obrazové paměti pak registrový pár HL, u něhož budeme zvyšovat hodnotu registru H (viz předchozí kapitolu):
; vstupy: DE - adresa prvního bajtu v ROM s maskou znaku
; HL - adresa obrazové paměti (popř. posunutá o offset)
; B - výška znaku (osm obrazových řádků pro standardní font uložený v ROM)
ld b, 8 ; počitadlo zapsaných bajtů
loop:
ld a, (de) ; načtení jednoho bajtu z masky
ld (hl), a ; zápis hodnoty na adresu (HL)
inc de ; posun na další bajt masky
inc h ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
13. Úplný zdrojový kód příkladu pro vykreslení znaku
Interní programová smyčka se nepatrně prodloužila, ovšem výsledek již vypadá velmi dobře:
Obrázek 18: Znak (v levém horním rohu) vykreslený nikoli rutinou v ROM, ale naší programovou smyčkou!
Následuje úplný zdrojový kód příkladu, který dokáže vykreslit libovolný znak, jenž je ovšem prozatím zadán primitivním způsobem – výpočtem offsetu od počátku paměťového bloku s maskami znaků:
SCREEN_ADR equ $4000
CHAR_ADR equ $3c00
ENTRY_POINT equ $8000
org ENTRY_POINT
start:
ld de, CHAR_ADR + 'A'*8 ; adresa masky znaku A
ld hl, SCREEN_ADR ; adresa pro zápis
ld b, 8 ; počitadlo zapsaných bajtů
loop:
ld a, (de) ; načtení jednoho bajtu z masky
ld (hl), a ; zápis hodnoty na adresu (HL)
inc de ; posun na další bajt masky
inc h ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
finish:
ret ; ukončit program
end ENTRY_POINT
Překlad do strojového kódu:
SCREEN_ADR EQU 4000
CHAR_ADR EQU 3C00
ENTRY_POINT EQU 8000
ORG 8000
8000: label start
8000:11083E LD DE, 3E08
8003:210040 LD HL, 4000
8006:0608 LD B, 08
8008: label loop
8008:1A LD A, (DE)
8009:77 LD (HL), A
800A:13 INC DE
800B:24 INC H
800C:10FA DJNZ 8008
800E: label finish
800E:C9 RET
800F: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 800E
14. Podprogram pro vykreslení libovolně zvoleného znaku na obrazovku
Nyní musíme program upravit tak, aby pro kód znaku (například pro „A“) vypočítal adresu v paměti ROM, na níž je uložena jeho maska, tj. osm po sobě jdoucích bajtů. Výpočet není nijak složitý, jen pochopitelně musíme přepsat násobení osmi instrukcemi pro bitové posuny doleva (přičemž je nutné případné přenosy převést do horního bajtu adresy):
; vstupy: A - ASCII kód znaku
; výstupy: HL - adresa masky znaku uložená v ROM
draw_char:
ld hl, CHAR_ADR ; adresa, od níž začínají masky znaků
ld b, 0
ld c, a ; kód znaku je nyní uložen ve dvojici BC
sla c
rl b
sla c
rl b
sla c
rl b ; vynásobení BC osmi
add hl, bc ; přičíst adresu k offsetu masky znaku
Výsledný podprogram použijeme následujícím způsobem:
SCREEN_ADR equ $4000
CHAR_ADR equ $3c00
ENTRY_POINT equ $8000
org ENTRY_POINT
start:
ld de, SCREEN_ADR ; adresa pro zápis
ld a, 'A' ; kód vykreslovaného znaku
call draw_char ; zavolat subrutinu pro vykreslení znaku
ld de, SCREEN_ADR+1 ; adresa pro zápis
ld a, 'B' ; kód vykreslovaného znaku
call draw_char ; zavolat subrutinu pro vykreslení znaku
ld de, SCREEN_ADR+128+31 ; adresa pro zápis
ld a, '?' ; kód vykreslovaného znaku
call draw_char ; zavolat subrutinu pro vykreslení znaku
finish:
ret ; ukončit program
draw_char:
ld hl, CHAR_ADR ; adresa, od níž začínají masky znaků
ld b, 0
ld c, a ; kód znaku je nyní ve dvojici BC
sla c
rl b
sla c
rl b
sla c
rl b ; vynásobení BC osmi
add hl, bc ; přičíst adresu k offsetu masky znaku
ld b, 8 ; počitadlo zapsaných bajtů
loop:
ld a, (hl) ; načtení jednoho bajtu z masky
ld (de), a ; zápis hodnoty na adresu (DE)
inc hl ; posun na další bajt masky
inc d ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
ret
end ENTRY_POINT
Obrázek 19: Znaky „A“, „B“ a „?“ vykreslené demonstračním příkladem.
SCREEN_ADR EQU 4000
CHAR_ADR EQU 3C00
ENTRY_POINT EQU 8000
ORG 8000
8000: label start
8000:110040 LD DE, 4000
8003:3E41 LD A, 41
8005:CD1980 CALL 8019
8008:110140 LD DE, 4001
800B:3E42 LD A, 42
800D:CD1980 CALL 8019
8010:119F40 LD DE, 409F
8013:3E3F LD A, 3F
8015:CD1980 CALL 8019
8018: label finish
8018:C9 RET
8019: label draw_char
8019:21003C LD HL, 3C00
801C:0600 LD B, 00
801E:4F LD C, A
801F:CB21 SLA C
8021:CB10 RL B
8023:CB21 SLA C
8025:CB10 RL B
8027:CB21 SLA C
8029:CB10 RL B
802B:09 ADD HL, BC
802C:0608 LD B, 08
802E: label loop
802E:7E LD A, (HL)
802F:12 LD (DE), A
8030:23 INC HL
8031:14 INC D
8032:10FA DJNZ 802E
8034:C9 RET
8035: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8034
15. Výpočet adresy v obrazové paměti pro vykreslení bloku
Ve třetí kapitole 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 bitů):
010 BB SSS RRR CCCCC BS: čí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ů
V případě, že budeme chtít vykreslovat znaky s maskou 8×8 pixelů, bude bitové pole SSS obsahovat nulové bity, protože číslo nejvyššího obrazového řádku znaku je rovno nule (pokud nebude znak vertikálně posunut). Formát se nám tedy zjednoduší do této podoby:
010 BB 000 RRR CCCCC
Ve chvíli, kdy máme dvojici souřadnic znaku [x,y], bude rozmístění jednotlivých bitů těchto souřadnic [X4 X3 X2 X1 X0, Y4 Y3 Y2 Y1 Y0] vypadat následovně:
0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
A takovou adresu je možné vypočítat poměrně snadno:
- Spodní tři bity y-ové souřadnice se posunou na pozice bitů 7, 6 a 5
- K těmto bitům se operací or přidají bity s x-ovou souřadnicí (takto vznikne spodní bajt adresy)
- Horní dva bity y-ové souřadnice jsou na správných místech, takže pouze operací and zamaskujeme ostatních pět bitů
- A instrukcí or přidáme prefixové bity 010 na pozice 7, 6 a 5 (takto vznikne horní bajt adresy)
Tento postup můžeme přepsat do formy podprogramu, kterému se v registru B předá x-ová souřadnice znaku a v registru C y-ová souřadnice. Podprogram vypočítá adresu prvního bajtu znaku a vrátí ji v registrové dvojici HL:
calc_block_address:
; parametry:
; B - x-ová souřadnice (ve znacích, ne pixelech)
; C - y-ová souřadnice (ve znacích, ne pixelech)
;
; návratové hodnoty:
; HL - adresa pro zápis bloku
;
; vzor adresy:
; 0 1 0 Y5 Y4 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
ld a, c
and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
rra
rra
rra
rra ; nyní jsou čísla řádků v horních třech bitech
or b ; připočítat x-ovou souřadnici
ld l, a ; máme spodní bajt adresy
; Y2 Y1 Y0 X4 X3 X2 X1 X0
ld a, c ; y-ová souřadnice
and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány)
or %01000000 ; "posun" do obrazové paměti (na 0x4000)
ld h, a ; máme horní bajt adresy
; 0 1 0 Y5 Y4 0 0 0
ret ; návrat z podprogramu
16. Vykreslení bloku 8×8 na libovolné místo na obrazovce
Nyní, když již máme k dispozici podprogram pro výpočet adresy znaku, můžeme tento podprogram použít v kódu, který na zadané místo na obrazovce vykreslí blok o velikosti 8×8 pixelů. Zkombinujeme tedy znalosti a techniky, které již známe. Pro ukázku toho, že je blok skutečně možné umístit na obrazovku do libovolného místa (v rastru 32×24 znaků) se vykreslí hned několik bloků. Povšimněte si způsobu předávání parametrů do podprogramů calc_block_address (registry B a C) a fill_block (registr A a registrový pár HL):
SCREEN_ADR equ $4000
ENTRY_POINT equ $8000
PATTERN equ $ff
org ENTRY_POINT
start:
ld b, 0 ; x-ová souřadnice
ld c, 0 ; y-ová souřadnice
call calc_block_address ; výpočet adresy
ld a, PATTERN
call fill_block ; vykreslit blok
ld b, 15 ; x-ová souřadnice
ld c, 12 ; y-ová souřadnice
call calc_block_address ; výpočet adresy
ld a, PATTERN
call fill_block ; vykreslit blok
ld b, 2 ; x-ová souřadnice
ld c, 2 ; y-ová souřadnice
call calc_block_address ; výpočet adresy
ld a, PATTERN
call fill_block ; vykreslit blok
ld b, 31 ; x-ová souřadnice
ld c, 23 ; y-ová souřadnice
call calc_block_address ; výpočet adresy
ld a, PATTERN
call fill_block ; vykreslit blok
finish:
jr finish ; žádný návrat do systému
calc_block_address:
; parametry:
; B - x-ová souřadnice (ve znacích, ne pixelech)
; C - y-ová souřadnice (ve znacích, ne pixelech)
;
; návratové hodnoty:
; HL - adresa pro zápis bloku
;
; vzor adresy:
; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
ld a, c
and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
rra
rra
rra
rra ; nyní jsou čísla řádků v horních třech bitech
or b ; připočítat x-ovou souřadnici
ld l, a ; máme spodní bajt adresy
; Y2 Y1 Y0 X4 X3 X2 X1 X0
ld a, c ; y-ová souřadnice
and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány)
or %01000000 ; "posun" do obrazové paměti (na 0x4000)
ld h, a ; máme horní bajt adresy
; 0 1 0 Y5 Y4 0 0 0
ret ; návrat z podprogramu
fill_block:
; parametry:
; A - pattern
; HL - adresa vykreslení bloku
ld b, 8 ; počitadlo zapsaných bajtů
loop:
ld (hl), PATTERN ; zápis hodnoty na adresu (HL)
inc h ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
ret ; návrat z podprogramu
end ENTRY_POINT
Podívejme se nyní na výsledek:
Obrázek 20: Několik bloků 8×8 vykreslených na různá místa na obrazovce.
Strojový kód získaný překladem z assembleru je již poměrně dlouhý – celých 73 bajtů :):
SCREEN_ADR EQU 4000
ENTRY_POINT EQU 8000
PATTERN EQU 00FF
ORG 8000
8000: label start
8000:0600 LD B, 00
8002:0E00 LD C, 00
8004:CD3280 CALL 8032
8007:3EFF LD A, FF
8009:CD4280 CALL 8042
800C:060F LD B, 0F
800E:0E0C LD C, 0C
8010:CD3280 CALL 8032
8013:3EFF LD A, FF
8015:CD4280 CALL 8042
8018:0602 LD B, 02
801A:0E02 LD C, 02
801C:CD3280 CALL 8032
801F:3EFF LD A, FF
8021:CD4280 CALL 8042
8024:061F LD B, 1F
8026:0E17 LD C, 17
8028:CD3280 CALL 8032
802B:3EFF LD A, FF
802D:CD4280 CALL 8042
8030: label finish
8030:18FE JR 8030
8032: label calc_block_address
8032:79 LD A, C
8033:E607 AND 07
8035:1F RRA
8036:1F RRA
8037:1F RRA
8038:1F RRA
8039:B0 OR B
803A:6F LD L, A
803B:79 LD A, C
803C:E618 AND 18
803E:F640 OR 40
8040:67 LD H, A
8041:C9 RET
8042: label fill_block
8042:0608 LD B, 08
8044: label loop
8044:36FF LD (HL), FF
8046:24 INC H
8047:10FB DJNZ 8044
8049:C9 RET
804A: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8049
17. Obsah navazujícího článku
V navazujícím článku nejprve dokončíme téma, kterému jsme se věnovali dnes. Máme již totiž k dispozici programový kód pro umístění a vykreslení bloku kamkoli na obrazovku a současně umíme vykreslit znak, jehož maska je přečtena z ROM a navíc umíme změnit hodnotu libovolného barvového atributu. Tyto tři znalosti je nutné zkombinovat a vytvořit rutinu pro tisk obarvených znaků popř. řetězců kamkoli na obrazovku. Posléze si ukážeme způsob realizace dalších vykreslovacích rutin. Začneme zdánlivě jednoduše – vykreslením pixelů – ale postupně si ukážeme i další algoritmy, například algoritmus pro vykreslení úsečky, kružnice (viz Elite) atd.
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 třech článcích [1] [2], [3], 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
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
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 | 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