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