Vývoj her a dem pro ZX Spectrum: vlastní vykreslovací subrutiny

2. 3. 2023
Doba čtení: 37 minut

Sdílet

 Autor: Depositphotos
V seriálu o vývoji pro legendární 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ů uložených v ROM.

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

17. Obsah navazujícího článku

18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/31-attributes.asm.

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.

Poznámka: toto rozdělení sice na první pohled může připadat poněkud zvláštní, ale jak uvidíme dále, do značné míry zjednodušuje subrutinu pro tisk znaků a řetězců (namísto složitého 16bitového součtu se použije jediná instrukce inc pro přechod na další obrazový řádek).

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/32-fill-in-vram.asm.

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/33-fill-in-vram-no-ret.asm.

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/34-fill-in-vram-pattern.asm.

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/35-slow-fill-in-vram.asm.

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/36-slow-fill-in-vram-no-ret.asm.

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

Poznámka: důležité je taktéž slovo subrutina, protože naznačuje, že se bude jednat o podprogramy volané instrukcí CALL, který se budou přes pracovní registry popř. přes zásobník předávat parametry.

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ý:

  1. Nastavíme pracovní adresu (pro zápis) na hodnotu 0×4000, což je první bajt obrazové paměti
  2. Nastavíme počitadlo smyčky na hodnotu 8, protože se má vykreslit osm obrazových řádků
  3. Na vypočtenou adresu se zapíše hodnota 0×ff. Tím se vykreslí osm horizontálních pixelů
  4. 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)
  5. Sníží se počitadlo smyčky o jedničku
  6. 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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/37-fill-block.asm.

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/38-fill-block-with-pattern.asm.

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
Poznámka: nyní je pravděpodobně patrné, že na první pohled „zmatená“ struktura obrazové paměti ZX Spectra je vlastně velmi promyšlená (s ohledem na požadavky BASICu a dalších textově orientovaných aplikací).

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/39-fill-block-optimized.asm.

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/40-draw-char.asm.

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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/41-draw-any-char.asm.

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:

  1. Spodní tři bity y-ové souřadnice se posunou na pozice bitů 7, 6 a 5
  2. K těmto bitům se operací or přidají bity s x-ovou souřadnicí (takto vznikne spodní bajt adresy)
  3. 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ů
  4. 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
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/42-block-anywhere.asm.

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.

bitcoin školení listopad 24

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

  1. z80 standalone assembler
    https://www.asm80.com/one­page/asmz80.html
  2. The ZX BASIC Compiler
    https://www.boriel.com/pages/the-zx-basic-compiler.html
  3. Z80 Assembly programming for the ZX Spectrum
    https://www.chibiakumas.com/z80/ZXSpec­trum.php
  4. 8-BIT SMACKDOWN! 65C02 vs. Z80: slithy VLOGS #6
    https://www.youtube.com/wat­ch?v=P1paVoFEvyc
  5. Instrukce mikroprocesoru Z80
    https://clrhome.org/table/
  6. Z80 instructions: adresní režimy atd.
    https://jnz.dk/z80/instructions.html
  7. Z80 Instruction Groups
    https://jnz.dk/z80/instgroups.html
  8. Elena, New programming language for the ZX Spectrum Next
    https://vintageisthenewold.com/elena-new-programming-language-for-the-zx-spectrum-next/
  9. Sinclair BASIC
    https://worldofspectrum.net/legacy-info/sinclair-basic/
  10. Grafika na osmibitových počítačích firmy Sinclair
    https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair/
  11. Grafika na osmibitových počítačích firmy Sinclair II
    https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/
  12. HiSoft BASIC
    https://worldofspectrum.net/in­foseekid.cgi?id=0008249
  13. YS MegaBasic
    https://worldofspectrum.net/in­foseekid.cgi?id=0008997
  14. Beta Basic
    https://worldofspectrum.net/in­foseekid.cgi?id=0007956
  15. BASIC+
    https://worldofspectrum.net/in­foseekid.php?id=0014277
  16. Spectrum ROM Memory Map
    https://skoolkit.ca/disas­semblies/rom/maps/all.html
  17. Goto subroutine
    https://skoolkit.ca/disas­semblies/rom/asm/7783.html
  18. Spectrum Next: The Evolution of the Speccy
    https://www.specnext.com/about/
  19. Sedmdesátiny assemblerů: lidsky čitelný strojový kód
    https://www.root.cz/clanky/sed­mdesatiny-assembleru-lidsky-citelny-strojovy-kod/
  20. Programovací jazyk BASIC na osmibitových mikropočítačích
    https://www.root.cz/clanky/pro­gramovaci-jazyk-basic-na-osmibitovych-mikropocitacich/
  21. Programovací jazyk BASIC na osmibitových mikropočítačích (2)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-basic-na-osmibitovych-mikropocitacich-2/#k06
  22. Programovací jazyk BASIC na osmibitových mikropočítačích (3)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-basic-na-osmibitovych-mikropocitacich-3/
  23. Sinclair BASIC (Wikipedia CZ)
    http://cs.wikipedia.org/wi­ki/Sinclair_BASIC
  24. Assembly Language: Still Relevant Today
    http://wilsonminesco.com/AssyDefense/
  25. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/gryga­rek/asm/asmlinux.html
  26. Why Assembly Language Programming? (Why Learning Assembly Language Is Still a Good Idea)
    https://wdc65×x.com/market­s/education/why-assembly-language-programming/
  27. Low Fat Computing
    http://www.ultratechnology­.com/lowfat.htm
  28. Assembly Language
    https://www.cleverism.com/skills-and-tools/assembly-language/
  29. Why do we need assembly language?
    https://cs.stackexchange.com/qu­estions/13287/why-do-we-need-assembly-language
  30. Assembly language (Wikipedia)
    https://en.wikipedia.org/wi­ki/Assembly_language#Histo­rical_perspective
  31. Assembly languages
    https://curlie.org/Computer­s/Programming/Languages/As­sembly/
  32. vasm
    http://sun.hasenbraten.de/vasm/
  33. B-ELITE
    https://jsj.itch.io/b-elite
  34. ZX-Spectrum Child
    http://www.dotkam.com/2008/11/19/zx-spectrum-child/
  35. Speccy.cz
    http://www.speccy.cz/
  36. Planet Sinclair
    http://www.nvg.ntnu.no/sinclair/
  37. World of Spectrum
    http://www.worldofspectrum.org/
  38. The system variables
    https://worldofspectrum.or­g/ZXBasicManual/zxmanchap25­.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.