Lulucat

A krétaeszköz teljesítménye: teljes képernyős passzoktól a scissor téglalapokig

Gaoge ZhangGaoge Zhang

A Lulucat Notes krétaeszköze a sűrű kézírásos területeken lelassult. A szűk keresztmetszetet nem a 3,571 bemeneti minta jelentette, hanem a képkockánkénti 70 teljes képernyős scratch pass. Egy elvetett alacsony felbontású gyorsítótár és a vonásonkénti scissor téglalap meséli el a történet többi részét.

A Lulucat Notes kivágott nézete iPaden 255%-os nagyítással: vörös és kék krétával írt klasszikus kínai kalligráfia, az alkalmazás eszköztárának egy részével.

Vörös és kék kréta az eszközön futó végső buildben, 155 vonás 255%-os nagyítással.

A Lulucat Notes krétaeszközének egy sajátos teljesítményproblémája volt: üres területen simának érződött az írás, de amikor egy már krétavonásokkal teli területre léptünk, a toll hegye lemaradt. Az ugyanazon a területen folytatott írás fokozatosan a vászon pásztázását is lelassította.

Egyetlen, hétköznapi kézírásos oldal elég volt a kiváltásához: 300%-os nagyítás, 70 látható krétavonás a helyi területen, összesen 3,571 bemeneti mintaponttal. Az üres területek folyamatosak maradtak; csak az a rész lassult le, ahol a vonások összegyűltek.

A javítás után ugyanaz az oldal 255%-os nagyítással is kaphat új írást, a meglévő vonások pedig teljes élességüket megőrzik mind a toll letétele, mind a pásztázás közben.

A Lulucat Notes egy iPaden 255%-os nagyítással, vörös és kék krétás kézírást mutatva. A szöveg: "天行健,君子以自强不息;地势坤,君子以厚德载物" — klasszikus kínai szöveg. Jobb felül egy kék Lulucat-maszkott látható. Az alsó eszköztár a 155-ös vonásszámot, a Save és Clear gombot, valamint egy 255%-os nagyítási csúszkát mutat.

A végleges eszköz-képernyőkép, összesen 155 vonással. Ezen a nagyítási szinten a meglévő vonások élessége sem a toll letételekor, sem pásztázás közben nem vált át ideiglenesen.

Miért van szüksége a krétának scratch textúrára?

Egy közönséges toll minden kör alakú lenyomatot közvetlenül a tintatextúrára komponálhat source-over keveréssel. A kréta egy szemcsézést vezérlő réteget ad hozzá: a renderelő először egy teljes vonás testének fedését és mélységét gyűjti össze, majd egy rögzített szemcsetextúrával meghatározza, mely pozíciók kapnak krétaport, végül pedig a meglévő tintára komponálja az eredményt.

Ez a scratch textúra egyetlen krétavonást szigetel el. A szigetelés azért fontos, mert ugyanazon vonáson belül a lenyomatok erősen átfednek; ha minden lenyomat külön kapna szemcsevezérlést, a vonás középvonala ismétlődő színt halmozna fel, a kréta pórusai pedig a mintavételi sűrűséggel együtt eltolódnának.

Nagy nagyításnál a Lulucat Notes újrarajzolja az aktuális viewportban látható vektoros vonásokat. A régi implementáció minden látható krétavonásnál a következő lépéseket végezte:

  1. A fő render encoder lezárása;
  2. A scratch textúra törlése;
  3. Ennek az egyetlen krétavonásnak a scratchbe rajzolása;
  4. A fő render encoder újbóli megnyitása;
  5. A scratch visszakomponálása a drawable-ra egy teljes képernyős háromszöggel.

Az iPad drawable-ja 2732×2048 volt — nagyjából 5.6 millió pixel. Minden krétavonás egy scratch passt és egy teljes képernyős kompozíciót indított. Hetven krétavonás körülbelül 141 render encodert és 70 teljes képernyős kompozíciót jelentett.

Legyen a látható krétavonások száma , a drawable pixelszáma pedig . Ha csak a pixel-lefedettséggel skálázódó munkát vesszük figyelembe, a régi implementáció megközelítőleg ilyen volt:

Minden krétavonáshoz fix render-pass többletköltség is tartozott, így ez a költség is lineárisan nőtt -nel. A 3,571 bemeneti pont csak másodlagos költséget jelentett. A helyi vonásszámmal az a teljes képernyős munka skálázódott, amelyet minden vonás kiváltott.

A méréseket egy 12.9 hüvelykes iPad Prón (5. generáció, M1), iPadOS 18.6.2 rendszeren végeztük. Ugyanannak a viewportnak a GPU-időbélyegeit hasonlítottuk össze a változtatás előtt és után, command-buffer időbélyegeket használva ugyanabban a Debug eszközbuildben ezen a konkrét iPaden — a továbbiakban LucasPad. Az alábbi tartományok több képkockás naplók jellemző ingadozásai, nem egy szállított verzió képkockasebesség-vállalásai. 70 látható krétavonásnál egyetlen képkocka jellemzően 52–60 ms GPU-időt igényelt; nagyjából 120 vonásnál a GPU-idő 77–80 ms-ra nőtt.

A scratch- és composite-passok teljes képernyős téglalapjainak területével becsülve a képkockánkénti elméleti munkatartomány körülbelül 783 millió pixelről 1.34 milliárd pixelre nőtt. Ez a téglalapterületek összege, és nem azonos a fragmenthívások számával, a videomemória olvasási/írási bájtjaival vagy a GPU hardveres számlálóival. A Metal fast clear művelete, az attachmentek betöltése/tárolása és a passváltás továbbra is a GPU és az illesztőprogram irányítása alatt marad.

Ez azt is megmagyarázza, miért maradtak folyamatosak az üres területek. A láthatósági szűrés kihagyja a viewporton kívüli vonásokat; üres területen közel van a nullához, sűrű területen viszont tovább növekszik.

Rossz válasz 0.85 ms-nál

Az alkalmazásnak már volt egy teljes oldalas tintatextúrája, amelyet pontonként két pixelre sütöttünk be. Megpróbáltuk ezt a textúrát közvetlenül megjeleníteni írás, pásztázás és nagyítás közben, miközben csak az aktuális Apple Pencil-vonást hagytuk élő vektorként; az interakció végén egy további képkocka újrarenderelte volna a nagy felbontású vektoros eredményt.

Ez a megközelítés nagyon jól teljesített. Ugyanazon a sűrű területen, 300%-os nagyításnál a GPU-idő 0.84–0.85 ms-ra csökkent, és többé nem nőtt a meglévő krétavonások számával.

A valódi eszközön a probléma ugyanilyen világos volt. 300%-os nagyításnál nagyjából hat képernyőpixelre volt szükség pontonként, a gyorsítótár azonban csak kettőt biztosított. Amint az Apple Pencil hozzáért a képernyőhöz, minden meglévő vonás lágy, alacsony felbontású képpé vált; a Pencil felemelésekor egy pillanat alatt visszaugrottak teljes élességükre.

A tesztelő ezt mondta: „Írás közben az egész vászon elmosódik. Amint felemelem a ceruzát, kitisztul.”

Az optimalizálást eltávolítottuk. A 0.85 ms volt a legalacsonyabb mért eredmény, de ettől még nem lett elfogadható krétaeszköz. A meglévő vonások az írás visszajelzésének részei; az élességük nem változhat a toll letételekor.

Minden krétavonás a saját téglalapjára korlátozva

A végleges javítás megtartotta a vonásonkénti scratchot és a vonásonkénti kompozitálást, és csak a pixelmunka területét csökkentette. Minden vonásnak már volt a vásznon egy befoglaló téglalapja, amelyet az összes lenyomat sugarának uniójából vezettünk le. A renderelő ezt a befoglaló téglalapot az aktuális viewport drawable-koordinátáira alakítja, és két pixellel kibővíti az antialiasing margója miatt:

Ugyanezt a scissor téglalapot használjuk három dologra: a scratch törlésére, a vonás megrajzolására és az eredmény visszakomponálására a fő felületre.

let rect = displayScissorRect(for: stroke.bounds, viewport: viewport)

scratchEncoder.setScissorRect(rect)
clearScratchExplicitly()
drawStrokeIntoScratch(stroke)

mainEncoder.setScissorRect(rect)
compositeChalkFromScratch(stroke)
mainEncoder.setScissorRect(fullDrawable)

Ugyanezt a logikát használjuk a 4096²-es tintatextúrára történő besütéshez és részleges újrajátszáshoz is, így a nagy zoomú megjelenítés és a rögzült tintaréteg nem produkál két eltérő krétaviselkedést.

Két részletet könnyű szem elől téveszteni.

Először: egy render pass loadAction = .clear művelete az attachment betöltési szakaszában történik, és nem korlátozza a rasterizációs scissor. Ha továbbra is ezt használnánk, az továbbra is az egész scratch textúrát törölné. A javított pass .dontCare értéket használ, majd egy clear_fragment-et rajzol a scissoron belül. Ezt a téglalapot ezután teljes egészében felülírjuk, a composite pedig csak ugyanebből a téglalapból olvas, így nem kell betölteni az attachment régi tartalmát.

Másodszor: minden krétavonás kompozitálása után vissza kell állítani a külső scissort. Ha kimarad ez az állapot-visszaállító sor, a következő tollak, képek vagy kijelölések továbbra is az előző krétavonás határai szerint lesznek levágva, és hiányzó vonásoknak vagy képeknek tűnnek.

A kréta szemcséje továbbra is abszolút vászonkoordinátákból mintavételeződik, nem a téglalapon belüli helyi UV-kból. A scissor mozgatása csak azt változtatja meg, mely pixeleket dolgozza fel a GPU; nem változtatja meg, hogy az egyes pixelek a szemcsetextúra melyik helyéről olvasnak. A szomszédos téglalapok ezért nem hoznak létre textúravarratokat, és a vászon húzása sem úsztatja el a szemcsét.

Ha csak a pixelterhelést vesszük figyelembe, az új munkatartomány közel áll a következőhöz:

ahol az aktuális képernyőn az -edik krétavonás tengelyekkel párhuzamos befoglaló téglalapjának területe. A render encoderek száma nem csökkent, de minden törlés és composite most már a vonás képernyőn látható befoglaló téglalapára korlátozódik.

Miért nem csoportosítottuk az azonos színű krétavonásokat?

Az oldalon található krétavonások többségének színe és sűrűsége azonos, ezért csábító lenne egyszerre több tucat vonást a scratchbe rajzolni, és csak egyszer kompozitálni. Ez tovább csökkentené a render passok számát, de átfedő területeken megváltoztatná a szín és a szemcse szemantikáját.

Vegyünk egy szándékosan leegyszerűsített esetet: két vonás egy adott pixelben pontosan ugyanazt a grain-gate értéket, -t használja, testfedettségük pedig és . A tényleges shaderben a gate az egyes vonások nyomásmélységétől is függ; ez az egyszerűbb eset önmagában is elég annak bemutatására, hogy a kötegelt feldolgozás általában nem ekvivalens. A jelenlegi, vonásonkénti kompozitálás eredménye

míg a testek előzetes összevonása és egyetlen gate alkalmazása ezt adja:

A különbség . Amikor két vonás átfed, és a grain gate nem tiszta nulla vagy tiszta egy, az eredmények eltérnek. A közvetlen csoportosítás megváltoztatná, hogyan rakódik le a krétapor a metszéseknél.

A pontos csoportosításhoz bizonyítani kellene, hogy a vonások pixelei kölcsönösen diszjunktak, vagy minden vonáshoz külön atlaszterületet kellene kiosztani, majd az eredeti sorrendben kompozitálni. A végleges eszközön végzett elfogadási teszt megtartotta a scissor megközelítést, ezért ebben a körben nem vezettünk be atlaszt és a kezelésének összetettségét sem.

Egymilliárd pixelből vissza néhány millióba

A végleges eszközmérések:

EsetJavítás előttPrecíz scissor
70 látható krétavonás, 300%-os nagyítás, írásGPU 52–60 ms≈ 9–10 ms
≈ 121 látható krétavonás, 300%-os nagyításGPU 77–80 ms13.7–15.6 ms
Elméleti téglalap-munkatartomány képkockánként (scratch + composite)783 M–1.34 B pixel≈ 1.7 M–3 M pixel

A vágási határokat egy eszközön készült dokumentum 3,452 vonásával és 202,710 mintapontjával is ellenőriztük. 0.5×, 1×, 2×, 3×, 5× és 8× nagyításnál 186,408 viewport-esetet generáltunk; minden olyan point sprite, amely nem nulla fedést hozhatott létre, a kiszámított scissoron belül maradt. Az ellenőrzés lefedte a vászon széleit, a viewport széleit és az offsetek különböző kombinációit.

A végleges kód nem vált alacsony felbontású LOD-ra az interakció állapota alapján. Alacsony nagyításnál továbbra is a teljes oldalas tintatextúra jelenik meg; nagy nagyításnál továbbra is vektorként rajzoljuk újra a látható vonásokat. A küszöb ugyanazon oldalán a toll letétele és a pásztázás nem cseréli le a meglévő vonásokat más élességi szintre. A nagy zoomú vektoros újrarajzolás során minden krétavonás scratch-törlése és kompozitálása csak a saját, képernyőn látható befoglaló téglalapját fedi le.

A GPU-idő nem mérte az élességet

A GPU-teljesítmény problémái nem feltétlenül az adatstruktúra leglátványosabb mennyiségével skálázódnak. Ebben az esetben a 3,571 bemeneti pont kézenfekvő gyanúsított volt; a képkockaidőt az egyes 70 krétavonások által kiváltott teljes képernyős munka, valamint a hozzájuk tartozó render-pass váltások határozták meg.

A vizuális szemantika is korlátozta a rendelkezésre álló optimalizálásokat. A vonásonkénti scratchot, az eredeti kompozitálási sorrendet és az abszolút vászonbeli szemcsekoordinátákat nem lehetett tetszőlegesen eltávolítani. Azonos szín és azonos sűrűség csak azt jelenti, hogy a paraméterek egyeznek — nem bizonyítja, hogy az átfedő eredmények összevonhatók.

A valódi eszközről érkező visszajelzés elutasította a legalacsonyabb GPU-időt adó verziót. Az „egész vászon elmosódik” megjegyzés megadta azt a termékoldali korlátot, amelyet a mérés önmagában nem fejezett ki: amikor az Apple Pencil hozzáér a képernyőhöz, a felhasználók a meglévő vonásokat is figyelik.

A végsőként ellenőrzött megvalósítás nem vezet be új interakciós cache-réteget, és nem csökkenti az élességet. A nagy zoomú vektoros újrarajzolás egyszerűen a saját, képernyőn látható befoglaló téglalapjára korlátozza minden krétavonás munkáját. Miután ismét betöltöttük LucasPadre, a visszajelzés ez lett: „Nagyon jól néz ki.”