NextDash: a je to venku
10. 8. 2026

V předchozím článku jsem popsal režim LoRes neboli Radastan: 128 krát 96 bodů, šestnáct barev a příjemně lineární framebuffer. Při psaní dema pro Forever 2027 jsem ale několikrát potřeboval spočítat ještě méně bodů. A tak jsem sešel o patro níž. Ne do tajného registru ZX Spectrum Next, ale k malé softwarové lsti.

Super LoRes na ZX Spectrum Next

V předchozím článku jsem popsal režim LoRes neboli Radastan: 128 krát 96 bodů, šestnáct barev a příjemně lineární framebuffer. Při psaní dema pro Forever 2027 jsem ale několikrát potřeboval spočítat ještě méně bodů. A tak jsem sešel o patro níž. Ne do tajného registru ZX Spectrum Next, ale k malé softwarové lsti.

ZX Spectrum Next / renderovací techniky

Super LoRes: když jsou i malé pixely příliš velká práce

Jak nad Radastanem postavit virtuální obraz 64 × 48, proměnit jediný výpočet v blok 4 × 4 bodů a získaný čas věnovat plasmě, SDF objektu nebo jiné grafické nepleše.

64 × 48 počítaných vzorků
4 × 4 výsledný blok na obrazovce
3072 výpočtů pro celý snímek
16 barev stále z palety LoRes
1

Nejdřív malé varování: není to nový režim Nextu

Název Super LoRes používám jako pracovní označení pro virtuální plochu 64 krát 48 bodů. Hardware o ní vůbec neví. Next stále zobrazuje obyčejný LoRes 128 krát 96 a stále čte 6144 bajtů z adresy $4000 nebo $6000. Jen renderer počítá obraz v poloviční šířce i výšce a každý výsledek zapíše do čtveřice LoRes pixelů.

Normální LoRes pixel se hardwarově zobrazí jako čtverec 2 krát 2 bodů aktivní plochy. Když softwarově spojíme dva LoRes pixely vedle sebe a dva pod sebou, dostaneme výsledný blok 4 krát 4. Rozměr 64 krát 48 tedy není nastavením registru, ale souřadnicovým systémem našeho efektu. Klidně bychom si mohli vytvořit 32 krát 24 s bloky 8 krát 8, ale to už by obraz připomínal barevnou mozaiku viděnou přes zamlžené potápěčské brýle.

Proč to vůbec dělat?

Běžný LoRes obraz má 12 288 pixelů. Super LoRes počítá jen 3072 vzorků, tedy přesně čtvrtinu. U drahé matematiky — vzdálenostních polí, deformací, bump mappingu nebo tunelu — je to velmi příjemná sleva. Framebuffer ani počet nutných výstupních bajtů se nezmenší; šetří se hlavně počet výpočtů efektu.

Renderer Spočítá barvu jednoho vzorku v mřížce 64 × 48. 1 logický bod
Software Rozmnoží jej na 2 × 2 pixely framebufferu LoRes. 2 × 2 LoRes
Hardware Nextu Každý LoRes pixel roztáhne na 2 × 2 body monitoru. 4 × 4 na výstupu
; Zapnuti je naprosto stejne jako u bezneho LoRes
nextreg $15,$80    ; povolit LoRes vrstvu
nextreg $6A,$20    ; Radastan 128x96, obrazovka $4000
nextreg $26,0      ; horizontalni posun
nextreg $27,0      ; vertikalni posun
2

Jak z jedné barvy vznikne blok 4 × 4

LoRes ukládá dva vodorovné pixely do jednoho bajtu. Horní nibble je barva levého bodu, dolní nibble barva pravého. Chceme-li oba body stejně barevné, stačí index barvy n zkopírovat do obou nibblů. Pro barvu $A tedy zapíšeme bajt $AA. Tím máme vodorovnou polovinu práce hotovou.

Jeden vypočtený vzorek
barva A

Souřadnice efektu: x, y

Výsledek na aktivní ploše 256 × 192
AAAA AAAA AAAA AAAA

2 × 2 softwarově, každý bod potom 2 × 2 hardwarově

Vodorovné zdvojení
1010 1010
LoRes pixel 2xLoRes pixel 2x+1
Složení bajtu packed = (n << 4) | n $A → $AA

Svislé zdvojení je ještě přímočařejší: stejný bajt zapíšeme do aktuálního LoRes řádku a znovu o 64 bajtů níž. Jeden řádek LoRes má právě 64 bajtů, takže druhý zápis míří na stejnou vodorovnou pozici v následujícím řádku. Dvě stejné hodnoty $AA pod sebou tvoří 2 krát 2 LoRes pixely a hardware z nich udělá šestnáct stejně barevných bodů monitoru.

3

Adresa bez dělení, násobení a černé magie

Logické souřadnice Super LoRes jsou x = 0..63 a y = 0..47. Každý logický řádek zabere dva fyzické LoRes řádky po 64 bajtech. Adresa horního bajtu bloku je proto velmi jednoduchá:

Horní řádek bloku addr = base + y*128 + x
Dolní řádek bloku addr + 64

Při kreslení celé obrazovky není potřeba adresu pro každý bod znovu počítat. Ukazatel postupuje lineárně přes 64 vzorků. Na konci řádku už stojí na začátku jeho svislé kopie, takže k němu přičteme dalších 64 a ocitneme se na začátku příštího logického řádku. Právě tento detail vysvětluje instrukci add ix,bc v renderovací smyčce níže.

$4000–$57FFprvní LoRes framebuffer, přesně 6144 bajtů
$5800–$5FFFmezera — není součástí prvního framebufferu
$6000–$77FFdruhý LoRes framebuffer, přesně 6144 bajtů
Pozor na mezeru mezi obrazovkami

Oba buffery neleží těsně za sebou. Nemažte jedním velkým blokem celé $4000–$77FF. Oblast $5800–$5FFF může obsahovat atributy, systémové proměnné nebo jiná data. V demu čistím zvlášť 6144 bajtů od $4000 a zvlášť 6144 bajtů od $6000.

4

Renderovací smyčka, která dělá tu důležitou práci

Nejdříve si připravíme tabulku zarovnanou na hranici 256 bajtů. Pro indexy 0 až 15 vrací hodnoty $00, $11, $22 ... $FF. Díky zarovnání zůstává adresa tabulky v registru H a barevný index stačí vložit do L. Místo několika posunů a logického OR pak máme jediný lookup. Z80 má tabulky rád. Kdyby mohl, pravděpodobně by si do tabulky předpočítal i přestávku na kávu.

    ALIGN 256
lores_expand_nibble:
    db $00,$11,$22,$33,$44,$55,$66,$77
    db $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
    ASSERT low lores_expand_nibble == 0

Následující smyčka je zjednodušenou podobou kódu, který používám. Zdroj obsahuje 3072 bajtů — jeden paletový index pro každý bod 64 krát 48. DE ukazuje na zdroj, IX na začátek skrytého LoRes framebufferu.

; DE = 64x48 zdroj, jeden index 0..15 na bajt
; IX = $4000 nebo $6000, podle zvoleneho zadniho bufferu

    ld bc,$0040                  ; 64 bajtu na LoRes radek
    ld iy,$3040                  ; IYH=48 radku, IYL=64 bodu
    ld h,high lores_expand_nibble

.row:
    ld iyl,64
.pixel:
    ld a,(de)                    ; barva 0..15
    inc de
    ld l,a
    ld a,(hl)                    ; n se zmeni na nn
    ld (ix+0),a                  ; dva LoRes pixely vedle sebe
    ld (ix+64),a                 ; a stejna dvojice o radek niz
    inc ix
    dec iyl
    jr nz,.pixel

    add ix,bc                    ; preskocit prave vyplnenou kopii
    dec iyh
    jr nz,.row
    ret

U procedurálního efektu nemusí existovat žádný 3KB zdroj. Smyčka vypočítá barvu a okamžitě ji rozbalí přes stejnou tabulku. Přesně tak pracuje část SDF Metamorph: pro každý bod načte předpočítaný poloměr, úhel a světlo, vyhodnotí povrch a výsledný čtyřbitový materiál rovnou zapíše do dvou řádků zadního bufferu. Mezikrok v paměti by tam jen zdržoval.

5

Co předpočítat a co nechat na Z80N

Super LoRes neurčuje, odkud se barvy vezmou. Lze je počítat živě, načítat z hotových snímků, skládat z map a tabulek nebo všechny přístupy promíchat. Volba je hlavně obchod mezi pamětí, časem procesoru a tím, zda se efekt musí měnit podle vstupu či hudby.

A / ŽIVĚ Počítat a rovnou kreslit

Minimum snímkových dat, maximum volnosti. Hodí se pro SDF, tunely, částice a zpětnou vazbu.

B / 3 KB Jeden index na bajt

Nejrychlejší čtení a jednoduchý lookup. Dva 64×48 snímky se vejdou do jednoho 8KB MMU page.

C / 1,5 KB Dva indexy na bajt

Poloviční data, ale před kreslením je nutné rozdělit horní a dolní nibble.

Podoba jednoho snímkuVelikostVýhodaCena
64 × 48, 8 bitů na index3072 BJednoduchá a rychlá expanzePolovina bajtu zůstává nevyužitá
64 × 48, 4 bity na index1536 BVelmi úsporné animaceRozbalování obou nibblů
Hotový LoRes 128 × 966144 BPřímé kopírování do framebufferuŽádná úspora dat
Procedurální snímek0 B snímkůAnimace reaguje za běhuVýpočet musí stihnout frame

V demu často používám variantu 3072 bajtů. Dva snímky zaberou 6144 bajtů, takže sdílejí jeden 8KB MMU page a po namapování do $E000 se vybírají adresou $E000 nebo $EC00. U hotové animace je to příjemný kompromis: změna stránky je levná, zdroj se čte lineárně a vnitřní smyčka nemusí rozbalovat dva různé pixely z jednoho bajtu.

6

Kreslíme dozadu, ukazujeme až hotový obraz

Vím, že je to možná zbytečné, protože každý kdo programoval a využíval 2. VRAM na ZX Spectru 128k, mu je práce se stínovou VRAM známa... ale pro jistotu. :) Super LoRes používá stejné dvě obrazovky jako Radastan. Bit 4 registru $6A vybírá viditelný framebuffer. Pokud je na monitoru $4000, renderer zapisuje do $6000 a naopak. Po dokončení celého snímku počká na přerušení, přepne bit a role obou stránek prohodí.

; visible_page obsahuje 0 nebo $10
    call render_superlores_frame   ; kresli do opacne stranky
    halt                           ; pockej na hranici snimku
    ld a,(visible_page)
    xor $10                        ; prohod $4000 / $6000
    ld (visible_page),a
    or $20                         ; zachovej zapnuty Radastan
    nextreg $6A,a

Je důležité rozlišovat stránku, která je vidět, a stránku, do které se kreslí. Proměnná se může jmenovat jakkoli, ale její význam musí zůstat konzistentní. V mých efektech visible_page popisuje právě zobrazenou stránku a výběr cílové adresy proto záměrně volí tu druhou. Tohle je drobnost, která po půlhodině ladění začne působit jako velmi osobní spor mezi programátorem a registrem $6A.

Snímek obrazovky 2026-08-09 194507

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *