Rajzoljunk betűket a képernyőre - mi lehet ennél egyszerűbb
A dolog úgy indult, hogy Go-t kellene tanulnom. Azt mondják a polyglotok (akik az átlagnál sokkal több nyelven tudnak beszélni), hogy a legjobb módszer a nyelvtanulásra az, ha úgy tanuljuk, hogy valami élvezetes dolgot csinálunk vele. Van, aki filmeket néz az adott nyelven, mások olvasnak, megint mások kártya segítségével tanulnak szavakat, és vannak, akik a mélyvízbe ugranak és nulla szókinccsel elkezdenek beszélni.
Hiszem, hogy ezt a módszert a programozási nyelvekre is lehet alkalmazni, így a képlet eredménye az lett, hogy elkezdek írni egy szöveges módú fájlkezelőt. A legtöbben a Norton Commanderre emlékeznek (esetleg a Dos Navigatorra), de van egy aktívan fejlesztett alternatíva, a FAR (File and ARchive) Manager. A mai napig ezt a szoftvert úgy tartom nyilván, hogy ez képes a legjobban támogatni a produktivitást. Ez az alkalmazás az egyik fő oka, hogy nem váltok linuxra vagy mac-re.
A FAR Manager tárgyalásába nem megyek most bele, megérne egy önálló esszét.
Amiért le akarom cserélni
Bár a fejlesztése továbbra is aktív, egetrengető újdonságok nem nagyon jelennek meg benne. Mindeközben elkezdtem átnézni a konkurrens fájlkezelőket (van belőlük pár), amikben viszont vannak hasznos, a FAR-ból hiányzó dolgok. Lehetne plugineket írni, de nem mindenre, sokkal inkább a fő forráskódot kellene módosítani, ha értelmes módon akarnám ezeket hozzátenni. De az meg olyan nyelven van, amit nem beszélek (és egyelőre nincs is tervben).
Úgy döntöttem, hogy inkább nekiállok újraírni - mi sem egyszerűbb! Nyilván tudom, hogy ez egy nagyjából lehetetlen küldetés, de már hozzászoktam, hogy az ilyen projekteket nem fejezem be, így hát nagyot álmodtam, aztán jutok, amíg jutok. Ahogy egy híres ember mondta:
Célozd meg a Holdat! Még ha elhibázod is, a csillagok közt landolsz.
Az elképzelt fájlkezelő terve
Az titkos. Még mindig van esély, hogy megcsinálom, szóval nem szórom el az ötleteimet csak úgy! Két fő modulra viszont fényt derítek: bevitel kezelés (egér, billentyűzet) és kimenet kezelés (betűk rajzolása a képernyőre).
Bemenet
Az első modul relatív unalmas, miután átverekedtem magamat a konzolos bevitel minden buktatóján. Az egyik, hogy a Windows - annak érdekében, hogy közelebb hozza magához a linux ökoszisztémát és megteremtse a linux desktop évét - átvette a VT (azaz Virtual Terminal) beviteli- és kimeneti módot a parancssori (konzolos) ablakban. Ez annyit jelent, hogy a bevitel butább, a kimenet meg lassabb lett.
Miért buta? A VT egy nagyon régi protokoll, amit még akkor találtak ki, amikor mainframe-ek és valódi terminálok voltak. Viszont az azóta elmúlt 50 évben történt néhány technológiai fejlesztés, amiről a VT nem vett tudomást, részben azért, hogy tartsa a visszafelé kompatibilitást. Az egyik ilyen jól tettenérhető buktató például az, hogy amikor a felhasználó lenyomja a Shift gombot a billentyűzetén, akkor arról a VT-ben futó program nem kap értesítést, majd csak akkor, amikor egy betűt is lenyom hozzá a delikvens és mondjuk egy nagy "A" betű formájában érkezik meg a programhoz. Ez egy olyan kompromisszum, amit semmiképpen nem akartam megkötni, hiszen a cél nem egy butább, hanem egy okosabb fájlkezelő létrehozása.
A másik buktatót a Windows maga prezentálta a saját visszafelé-kompatibilitásával, amikor megpróbálta átvenni a speciális billentyűkombinációk (pl. Ctrl+C) kezelését. Mint kiderült, ez olyan edge case-eket eredményez, mint például, ha egymás után sorrendben lenyomtam a shift+ctrl+alt gombokat, majd fordított sorrendben felengedtem, akkor a felengedésről nem kapott értesítést a program, míg ha alt+ctrl+shift volt a lenyomás sorrendje, akkor minden esemény gond nékül megérkezett.
Szerencsére mindkét buktatóra volt érdemi megoldás, így a billentyűzetkezelés egészen nagy részben le is van már fedve.
Kimenet
A hosszú bevezető után térjünk rá a lényegre: a kimenet. Azaz, szögegyszerűen fogalmazva, rajzoljunk betűket a képernyőre. Erre a célre a konzolos felületen több módszer is rendelkezésre áll, az egyik, hogy a betűk listáját és a színkódokat felszórjuk a képernyőre hosszában-keresztben (WriteConsoleOutputW - régi módszer). A másik, hogy a színkódokat beépítjük a szövegbe (VT módszer) és soronként felírjuk a képernyőre (WriteConsoleW - új módszer). Utóbbinál elég hamar kiesik, hogy ez bizony lassabb, mint a régi módszer, úgy kb. fele a sebesség.
Miután elégedetlen voltam ezzel a teljesítménnyel a középkategóriás számítógépemen (értsd: a mai 3D-s játékok még elfutnak rajta), úgy gondoltam, kell, hogy legyen ennél jobb megoldás. Közben kaptam is ajánlásokat kollégáktól, hogy vannak már GPU-gyorsításra épülő betűrajzoló programok (terminálok) is, amik zökkenőmentes képfrissítést produkálnak. Más ötlet híján belenéztem ebbe a vonalba is.
Miután napokat kalapáltam a Go-t, fórumokat és LLM-eket kérdezgettem, kiderült, hogy a Go egyáltalán nem alkalmas ilyesmire. Így hát, dobtam a programnyelvet és lecseréltem egy olyanra, ami már bizonyított: C#-ra. (Aki azt találja mondani, hogy minden programnyelv alkalmas mindenre, azt kérem egy döglött heringgel arcon kínálni. Amúgy persze, alkalmas, csak milyen áron?)
Így az utazásom a .NET felé vette az irányt, amiben többek között komoly 3D-s játékokat is fejlesztenek, tehát ezzel nem futhatok lukra! Három módszerrel estem neki a megjelenítésnek. Az egyik a klasszikus grafikus eszköz kimenet, amit a Windows már 30 éve kínál: GDI (Graphics Device Interface). Azaz, fogtam a szöveget, amit ki akartam rakni a képernyőre és kiírás helyet megrajzoltattam a betűket. A GDI-ről annyit kell tudni, hogy az régen is és ma is alkalmas volt grafikus megjelenítésre dedikált videókártya nélkül is (ma már azért ritkán kapni ilyen gépet). Ez tehát elvileg lassabb, mintha a dedikált videókártyával, gyorsított módon rajzolnánk a képernyőre.
A következő próbálkozás a DirectX volt, ami konkrétan arra van, hogy fejlett, részletgazdag 3D-s játékokat maximális képfrissítés mellett futtasson. Gondoltam akkor, hogy ez a roppant bonyolult "rajzoljunk betűket a képernyőre" meg sem fog neki kottyanni, egyrészt mert gyors rajzolásra találták ki, másrész a feladat nem lehetne ennél egyszerűbb!
Az utolsó jelölt egy, szintén játékok számára készített rajzolási mód, az OpenGL utódja, a Vulkan megjelenítő volt. Nagyon sok megközelítésben hasonlít a a DirectX-hez, az egyik vitathatatlan előnye viszont, hogy nem csak Windows platformra van. Bár nem volt cél, de azért mégiscsak jobban hangzik, ha minden oprendszer támogatott.
Ígyhát csináltam is egy-egy alternatívát minden megközelítéssel, ami egyelőre szóba jött, hogy megmérjem, melyikre érdemes építeni az alkalmazásomat. A feladatot direkt nehezítettem: a szöveges alkalmazások többsége viszonylag kis szórással alkalmaz különböző szöveg- és háttérszíneket a megjelenítéskor, én mégsem akartam arra apellálni, hogy "egyszínű konzolban gyors, de ahogy bonyolultabb lesz, elkezd szaggatni". Ez egy olyan kompromisszum lett volna, amit szintén nem akartam meghozni.
Ezért a tesztképernyő valahogy így néz ki:
    
Én is tudom, hogy a valóságban erre nem igazán lesz példa, de úgy voltam vele, ha ezt elviszi, akkor mindent elvisz.
Beállítottam a konzolméretet 240x63 karakterre (ennyi fér ki a képernyőmre fullhd-ban 8x16-os ASCII betűtípussal) illetve 80x25-re (a klasszikus DOS képernyőméret) és lefuttattam a teszteket, hogy melyik milyen képújrarajzolási sebességre képes (FPS - frames per second):
| Megjelenítés | 240x63 | 80x25 | 
|---|---|---|
| WriteConsoleOutputW | 20.3 | 64.5 | 
| WriteConsoleW | 12.9 | 64.5 | 
| GDI | 22.2 | 64.5 | 
| Vulkan | 23.5 | 175.2 | 
| DirectX | 17.6 | 130.5 | 
A konklúzió az, hogy mindegyikkel gyötrelmesen rossz a képújrarajzolási sebesség (pedig már van benne némi optimalizáció is). Természetesen, valós kimenettel ezek a számok sokkal barátságosabban alakulnának, például fekete alapon fehér betűkkel:
| Megjelenítés | 240x63 | 80x25 | 
|---|---|---|
| WriteConsoleOutputW | 64.5 | 64.5 | 
| WriteConsoleW | 64.5 | 64.5 | 
| GDI | 62.4 | 64.5 | 
| Vulkan | 114.4 | 733.0 | 
| DirectX | 140.2 | 944.5 | 
Közelítsük meg máshogy
Elég világos, hogy a probléma elsősorban nem a grafikai felület rajzolási képességein van, hanem a Windows betűrajzoló algoritmusában. Így tehát egy unortodox megközelítéssel folytattam: vegyük le ezt a terhet (nagyrészben) a Windows-ról és miután megrajzolt egy betűt, tároljuk el a képét. A teszthez ugye a 8x16-os cellaméretű betűtípust használtam, és 128 pixelt eltárolni, mozgatni könnyebb, mint egy komplex ívekkel és koordinátákkal leírt karaktert újra és újra megrajzolni. Ezzel a feladat leegyszerűsödik textúramásolássá és -tárolássá, amivel nagyságrendi sebességnövekedést lehetett elérni (egy korlátig).
| Felbontás | Megjelenítés | FPS | 
|---|---|---|
| 240x63 | DirectX textúrázás | 66.4 | 
| 80x25 | DirectX textúrázás | 450.1 | 
Nincs jó megoldás
A textúrázás, bár jól hangzik a számok alapján, tartogat egy buktatót. A teljesítménye ugyanis nem optimalizálható szignifikánsan, míg a sima szövegírásé igen. Itt ugye arról van szó, hogy amikor betűk képét kell másolni ide-oda, akkor azokat csak egyesével lehet még akkor is, ha mindegyik egyszínű és ugyanolyan stílusú (félkövér, dőlt). Szöveget írni viszont, ha a szín és stílus is egyezik, lehet tömegével, ami jelentős gyorsulást eredményez. Például ha egy komplett sort ki lehet írni azonos tulajdonságokkal, akkor az 200x-os gyorsulást is eredményezhet, így elég hamar át lehet lépni a kezdeti korlátokon és a 20 FPS helyett (teoretikusan) 2000 FPS-sel írni a betűket. A gyakorlat azért nem mutat ilyen mértékű sebességkövekedést, csak 5-7x-est.
Összehasonlításképpen, extrém felhasználás:
| Felbontás | DX + betűírás | DX + textúra | változás | 
|---|---|---|---|
| 240x63 | 17.6 | 66.4 | 377% + | 
| 80x25 | 130.5 | 450.1 | 345% + | 
Összehasonlításképpen, normál felhasználás:
| Felbontás | DX + betűírás | DX + textúra | változás | 
|---|---|---|---|
| 240x63 | 140.2 | 66.4 | 47% - | 
| 80x25 | 944.5 | 450.1 | 47% - | 
Konklúzió, az minden cikk végére kell
Szóval úgy tűnik, hogy a lokális optimum az a DirectX + betűírás módszer marad, de az is egészen jó kihívás lenne, ha mondjuk opcióban hagynám a többi megjelenítőt is (ha komolyan gondolom például a multiplatform megjelenítést).