Zmogljivost orodja s kredo: od celozaslonskih prehodov do pravokotnikov scissor
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.

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.

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:
- Zaključiti glavni render encoder;
- Počistiti teksturo scratch;
- To eno potezo krede narisati v scratch;
- Ponovno odpreti glavni render encoder;
- 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
Vsaka poteza krede je imela tudi fiksni strošek render pass, zato je ta strošek prav tako linearno naraščal z
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
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
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
medtem ko združitev teles in nato uporaba ene same gate ustvari
Razlika je
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:
| Primer | Pred popravkom | Natančen scissor |
|---|---|---|
| 70 vidnih potez krede, 300% povečava, pisanje | GPU 52–60 ms | ≈ 9–10 ms |
| ≈ 121 vidnih potez krede, 300% povečava | GPU 77–80 ms | 13.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.«