Lulucat

Zmogljivost orodja s kredo: od celozaslonskih prehodov do pravokotnikov scissor

Gaoge ZhangGaoge Zhang

Orodje s kredo v Lulucat Notes se je na območjih z gostim rokopisom upočasnilo. Ozko grlo ni bilo 3,571 vhodnih vzorcev — predstavljalo ga je 70 celozaslonskih prehodov scratch na sličico. Zavrnjeni predpomnilnik nizke ločljivosti in pravokotnik scissor za vsako potezo dopolnjujeta zgodbo.

Obrezan pogled Lulucat Notes na iPadu pri 255% povečavi, ki prikazuje rdeče in modro pisanje s kredo, klasičen kitajski odlomek in del orodne vrstice aplikacije.

Rdeča in modra kreda na končni različici naprave, 155 potez pri 255% povečavi.

Orodje s kredo v Lulucat Notes je imelo natančno določeno težavo z zmogljivostjo: pisanje na praznem območju je bilo gladko, toda ko je pero prešlo na območje, že zapolnjeno s potezami krede, je konica začela zaostajati. Nadaljevanje pisanja na istem območju je postopoma upočasnilo tudi premikanje platna.

Za sprožitev težave je zadostovala ena stran običajnega rokopisa: 300% povečava, 70 vidnih potez krede na lokalnem območju, skupaj 3,571 vhodnih vzorčnih točk. Prazna območja so ostala tekoča; upočasnilo se je samo območje, kjer so bile poteze zgoščene.

Po popravku lahko ista stran pri 255% povečavi še naprej sprejema novo pisanje, obstoječe poteze pa ohranijo popolno jasnost tako ob dotiku peresa kot pri premikanju platna.

Lulucat Notes na iPadu pri 255% povečavi, ki prikazuje rdeče in modro pisanje s kredo. Besedilo se glasi »天行健,君子以自强不息;地势坤,君子以厚德载物« — klasičen kitajski odlomek. V zgornjem desnem kotu je modra maskota Lulucat. Spodnja orodna vrstica prikazuje število potez 155, gumba Save in Clear ter drsnik povečave 255%.

Končni posnetek zaslona naprave, skupaj 155 potez. Pri tej povečavi niti dotik peresa niti premikanje platna začasno ne spremenita jasnosti obstoječih potez.

Zakaj kreda potrebuje teksturo scratch

Navadno pero lahko vsak krožni odtis neposredno sestavi na teksturo črnila z mešanjem source-over. Kreda doda plast nadzora zrnatosti: izrisovalnik najprej zbere pokritost telesa in globino za celotno potezo, nato s fiksno teksturo zrnatosti določi, kateri položaji prejmejo prah krede, na koncu pa rezultat sestavi z obstoječim črnilom.

Ta tekstura scratch izolira eno samo potezo krede. Izolacija je pomembna, ker se odtisi znotraj iste poteze močno prekrivajo; če bi bil vsak odtis ločeno filtriran z zrnatostjo, bi sredinska črta poteze večkrat kopičila barvo, pore krede pa bi se spreminjale z gostoto vzorčenja.

Pri velikih povečavah Lulucat Notes ponovno izriše vektorske poteze, vidne v trenutnem viewportu. Stara izvedba je za vsako vidno potezo krede izvajala te korake:

  1. Zaključiti glavni render encoder;
  2. Počistiti teksturo scratch;
  3. To eno potezo krede narisati v scratch;
  4. Ponovno odpreti glavni render encoder;
  5. Scratch sestaviti nazaj v drawable s celozaslonskim trikotnikom.

Semantika ene poteze je bila pravilna, vendar je bil obseg dela veliko večji. Drawable iPada je imel 2732×2048 — približno 5.6 million pixels. Vsaka poteza krede je sprožila en prehod scratch in eno celozaslonsko sestavljanje. Sedemdeset potez krede je pomenilo približno 141 render encoders in 70 celozaslonskih sestav.

Naj bo število vidnih potez krede , število slikovnih pik drawable pa . Če upoštevamo samo delo, ki se spreminja s pokritostjo slikovnih pik, je bila stara izvedba blizu

Vsaka poteza krede je imela tudi fiksni strošek render pass, zato je ta strošek prav tako linearno naraščal z . 3,571 vhodnih točk je prispevalo le sekundarni strošek. Z lokalnim številom potez se je povečeval celozaslonski obseg dela, ki ga je sprožila vsaka poteza.

Meritve smo izvedli na 12.9-inch iPad Pro (5th generation, M1) z iPadOS 18.6.2. Primerjali smo GPU timestamps istega viewporta pred spremembo in po njej ter uporabili command-buffer timestamps v isti Debug device build na tem iPadu — v nadaljevanju LucasPad. Spodnji razponi so običajna nihanja iz dnevnikov z več sličicami, ne obljube o hitrosti sličic v različici za uporabnike. Pri 70 vidnih potezah krede je ena sličica običajno zahtevala 52–60 ms časa GPU; na območju s približno 120 potezami je čas GPU narasel na 77–80 ms.

Če obseg ocenimo po površini celozaslonskih pravokotnikov prehodov scratch in composite, je teoretični obseg dela na sličico narasel s približno 783 million pixels na 1.34 billion pixels. Ta vrednost je vsota površin pravokotnikov in ni enaka številu priklicev fragmentov, bajtom branja/pisanja videopomnilnika ali strojnim števcem GPU. Metalov fast clear, attachment load/store in preklapljanje passov ostajajo pod nadzorom GPU in gonilnika.

To pojasni tudi, zakaj so prazna območja ostala tekoča. Visibility culling preskoči poteze zunaj viewporta; na praznem območju je blizu nič, na gostem območju pa še naprej raste.

Napačen odgovor pri 0.85 ms

Aplikacija je že imela vnaprej pripravljeno teksturo črnila celotne strani pri dveh slikovnih pikah na točko. Poskusili smo to teksturo neposredno prikazovati med pisanjem, premikanjem in povečavo ter kot živi vektor ohraniti samo trenutno potezo Apple Pencila; po koncu interakcije bi dodatna sličica znova izrisala vektorski rezultat visoke ločljivosti.

Ta pristop je deloval zelo dobro. Na istem gostem območju pri 300% povečavi je čas GPU padel na 0.84–0.85 ms in ni več rasel s številom obstoječih potez krede.

Težava na dejanski napravi je bila prav tako jasna. Pri 300% povečavi je bilo potrebnih približno šest zaslonskih slikovnih pik na točko, predpomnilnik pa je zagotavljal samo dve. Ko se je Apple Pencil dotaknil površine, so se vse obstoječe poteze spremenile v mehko sliko nizke ločljivosti; po dvigu Pencila so se vrnile v popolno jasnost.

Tester je povedal eno stvar: »Ko pišem, se celotno platno zamegli. Ko spustim, se spet izostri.«

Optimizacijo smo odstranili. 0.85 ms je bil najnižji izmerjeni rezultat, vendar ni bil sprejemljivo orodje s kredo. Obstoječe poteze so del povratne informacije pri pisanju; njihova jasnost se ob dotiku peresa ne sme spremeniti.

Omejitev vsake poteze krede na njen lastni pravokotnik

Končni popravek je ohranil scratch in sestavljanje za vsako potezo posebej ter zmanjšal samo obseg dela s slikovnimi pikami. Vsaka poteza je že imela omejitveni okvir na platnu, izpeljan iz unije vseh polmerov njenih odtisov. Izrisovalnik ta okvir pretvori v koordinate drawable trenutnega viewporta in ga zaradi roba za antialiasing razširi za dve slikovni piki:

Isti pravokotnik scissor se uporablja za tri stvari: čiščenje scratch, risanje poteze in sestavljanje rezultata nazaj na glavno površino.

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

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

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

Ista logika se uporablja tudi pri pečenju in delnem predvajanju na teksturi črnila 4096², zato prikaz pri veliki povečavi in ustaljena plast črnila ne ustvarjata dveh različnih vedenj krede.

Tu je mogoče spregledati dve podrobnosti.

Prvič, loadAction = .clear v render passu se izvede med nalaganjem attachmenta in ga rasterizacijski scissor ne omejuje. Če bi ga še naprej uporabljali, bi še vedno počistil celotno teksturo scratch. Popravljeni pass uporablja .dontCare, nato pa znotraj scissorja nariše clear_fragment. Ta pravokotnik je nato v celoti zapisan, composite pa bere samo isti pravokotnik, zato stare vsebine attachmenta ni treba naložiti.

Drugič, po koncu sestavljanja vsake poteze krede je treba obnoviti zunanji scissor. Če izpustimo to vrstico za obnovitev stanja, bodo naslednja peresa, slike ali izbori še naprej izrezani z mejami prejšnje poteze krede in bodo videti kot manjkajoče poteze ali slike.

Zrnatost krede se še vedno vzorči iz absolutnih koordinat platna, ne iz lokalnih UV. Premik scissorja spremeni samo pike, ki jih obdela GPU; ne spremeni mesta v teksturi zrnatosti, iz katerega bere posamezna pika. Sosednji pravokotniki zato ne ustvarijo šivov v teksturi, vlečenje platna pa ne povzroči premikanja zrnatosti.

Če upoštevamo samo obremenitev slikovnih pik, je novi obseg dela blizu

kjer je površina osno poravnanega omejitvenega okvirja -te poteze krede na trenutnem zaslonu. Število render encoders se ni zmanjšalo, vendar sta vsako čiščenje in sestavljanje zdaj omejena na zaslonski omejitveni okvir poteze.

Zakaj potez krede iste barve nismo združili v paket

Večina potez krede na strani ima isto barvo in gostoto, zato je vabljivo več deset potez narisati v scratch hkrati in jih sestaviti samo enkrat. To bi še zmanjšalo število render passov, vendar bi spremenilo semantiko barve in zrnatosti na prekrivajočih se območjih.

Oglejmo si namerno poenostavljen primer: dve potezi imata na dani piki isto vrednost grain-gate , njuni pokritosti teles pa sta in . V dejanskem shaderju je gate odvisen tudi od globine pritiska vsake poteze; ta preprostejši primer zadošča za dokaz, da paketno risanje na splošno ni enakovredno. Trenutno sestavljanje po posameznih potezah ustvari

medtem ko združitev teles in nato uporaba ene same gate ustvari

Razlika je . Ko se dve potezi prekrivata in grain gate ni niti čista ničla niti čista enica, se rezultata razlikujeta. Neposredno paketno risanje bi spremenilo, kako se prah krede odlaga na križanjih.

Natančno paketno risanje zahteva dokaz, da so slikovne pike potez medsebojno disjunktne, ali pa dodelitev neodvisnega območja atlasa vsaki potezi in sestavljanje v prvotnem vrstnem redu. Končni sprejem na napravi je ohranil pristop scissor, zato v tem krogu nismo uvedli atlasa ali zapletenosti njegovega upravljanja.

Od milijarde slikovnih pik nazaj do nekaj milijonov

Končne meritve naprave:

PrimerPred popravkomNatančen scissor
70 vidnih potez krede, 300% povečava, pisanjeGPU 52–60 ms≈ 9–10 ms
≈ 121 vidnih potez krede, 300% povečavaGPU 77–80 ms13.7–15.6 ms
Teoretični obseg pravokotnikov na sličico (scratch + composite)783 M–1.34 B pixels≈ 1.7 M–3 M pixels

Meje izrezovanja smo preverili tudi z 3,452 potezami in 202,710 vzorčnimi točkami iz dokumenta naprave. Pri povečavah 0.5×, 1×, 2×, 3×, 5× in 8× je bilo ustvarjenih 186,408 primerov viewporta; vsaka point sprite, ki bi lahko ustvarila pokritost, različno od nič, je bila znotraj izračunanega scissorja. Preverjanje je zajelo robove platna, robove viewporta in različne kombinacije odmikov.

Končna koda se glede na stanje interakcije ne preklopi na LOD z nizko ločljivostjo. Pri majhnih povečavah še vedno prikazuje teksturo črnila celotne strani, pri velikih povečavah pa še vedno vektorsko ponovno izriše vidne poteze. Na isti strani praga dotik peresa in premikanje ne zamenjata obstoječih potez z drugo stopnjo jasnosti. Med vektorskim ponovnim izrisom pri veliki povečavi čiščenje scratch in sestavljanje vsake poteze krede pokrivata samo njen lastni zaslonski omejitveni okvir.

GPU čas ni zajel jasnosti

Težave z zmogljivostjo GPU se ne povečujejo nujno z najbolj vidno količino v podatkovni strukturi. V tem primeru je bilo 3,571 vhodnih točk lahek osumljenec; čas sličice je določalo celozaslonsko delo, ki ga je sprožila vsaka od 70 potez krede, skupaj s preklapljanjem render-passov.

Vizualna semantika je omejila tudi razpoložljive optimizacije. Scratch za vsako potezo, prvotni vrstni red sestavljanja in absolutnih koordinat zrnatosti platna ni bilo mogoče poljubno odstraniti. Ista barva in ista gostota pomenita samo, da se parametri ujemajo — ne dokazujeta, da je mogoče prekrivajoče se rezultate združiti.

Povratne informacije z dejanske naprave so zavrnile različico z najkrajšim časom GPU. Opomba »celotno platno se zamegli« je izrazila produktno omejitev, ki je samo merjenje ni pokazalo: ko se Apple Pencil dotakne površine, uporabnik opazuje tudi obstoječe poteze.

Končna različica ne uvaja nove plasti predpomnilnika interakcije in ne zmanjša jasnosti. Vektorski ponovni izris pri veliki povečavi samo omeji delo vsake poteze krede na njen lastni zaslonski omejitveni okvir. Po ponovnem nalaganju na LucasPad je bil odziv: »Videti je odlično.«