Ponovna izgradnja izrisovalnega cevovoda Lulucat Notes s Kimi K3
Naš cevovod ploščic v Core Graphics je v šestih majhnih korakih postal cevovod točkastih spriteov v Metalu, vsak preverjen na pravem iPadu. Kodo smo parno programirali s Kimi K3 na Fireworks.
Prejšnji teden je vlečenje izbire z lasom po Lulucat Notes ustvarilo vidno valovanje: nekatere ploščice zaslona so v istem okvirju prikazovale izbiro na novem položaju, druge pa še vedno starega. Celoten izrisovalni cevovod — bitno sliko Core Graphics plus CATiledLayer — smo zamenjali z Metalom v šestih majhnih korakih, vsak korak pa preverili na pravem iPadu, preden se je začel naslednji.
Še ena beseda o avtorstvu, ker je pomembna za drugo polovico te objave: kodo smo parno programirali s Kimi K3, odprtim modelom Moonshot, ki teče na Fireworks. Človek je vodil, odločal in preverjal; model je napisal skoraj vsako vrstico.
Cevovod
Stari cevovod je imel dva načina risanja, ki sta se počasi razšla: poteze so bile za poceni prikaz zapečene v bitno sliko, kadar pa so ploščice potrebovale več podrobnosti, znova izrisane kot vektorji. Novi cevovod ima natanko eno idejo: vse, kar je podobno črnilu, je točkasti sprite. Poteza peresa, poteg označevalnika in dotik radirke so isto 32-bajtno oglišče — položaj, premer, barva —, ki ga isti par senčilnikov izriše kot na GPU-ju rasterizirane kroge, razporejene v koraku ene točke vzdolž dolžine loka poteze.
![]()
Pri razmiku ene točke se veriga krogov od matematično popolne kapsule razlikuje za približno 0.075 točke — petino slikovne pike pri naši gostoti platna. V zameno se tri orodja strnejo v eno kodno pot, GPU pa počne to, v čemer je najboljši.
Okoli te ideje je arhitektura preprosta. Potrjeno črnilo živi v eni sami teksturi 4096². UIScrollView je preživel, skrčen na čisti pogon gest: njegov contentOffset in zoomScale vsak okvir napajata uniformo vidnega območja, zato pomikanje in zumiranje ne zapisujeta ničesar. Vsak okvir je pet izrisov:
flowchart TB
subgraph frame["Every frame: five draws"]
direction TB
paper["1 · paper blit"] --> ink["2 · committed ink"]
ink --> live["3 · live stroke"]
live --> sel["4 · selection"]
sel --> dash["5 · lasso dashes"]
end
commit["stroke commit<br/>append stamps"] --> tex[("ink texture<br/>4096² render target")]
replay["regional replay<br/>erase · delete · move · undo"] --> tex
tex -. "sampled or re-drawn" .-> ink
Urejanja zapisujejo teksturo neposredno. Potrditev poteze doda njene odtise. Brisanje, odstranjevanje, premikanje in razveljavitev za prizadeto območje ponovno izrišejo z uporabo pravokotnika scissor: počisti območje, znova izriši poteze, ki se sekajo z njim, in končano. Delno brisanje ohranja semantiko lastništva iz prejšnje objave — brisanje pripada potezi, ki ji odstranjuje črnilo — tako, da vsako izbrisano potezo izriše v začasno teksturo, odšteje njene lastne poti brisanja z mešanjem destination-out (
Izbira, ki je vse to sprožila, je zdaj prav tako izrisana kot točkasti spritei. Njeno vlečenje posodobi eno uniformo zamika. Nič zapisov v teksturo, nobenih invalidacij ploščic — valovanje je strukturno odstranjeno, ne le ublaženo.
Vratar: primerjava slikovnih pik
Izrisovalnika Core Graphics nismo izbrisali. Prestavili smo ga v vlogo referenčne implementacije brez povezave in vsaka sprememba v Metalu mora na realnih podatkih potez, zajetih na napravi, prestati primerjavo slikovnih pik z njim. Merilo sprejema niso »identične slikovne pike« — dva pravilna rasterizatorja se lahko utemeljeno razhajata za nekaj stopenj sive vzdolž robov z glajenjem. Pogoj je strukturnen: nobenega manjkajočega črnila, nobenega zamika, nobenega barvnega drsenja in nobene velike razlike kjer koli stran od črnila.
![]()
Ta preizkus je ujel štiri od petih hroščev, na katere smo naleteli med gradnjo izrisovalnika brez povezave; vse smo diagnosticirali z obrezovanjem izhoda na raven slikovnih pik: neujemanje koraka strukture Swift/Metal (28 bajtov proti 32, ker Metal poravna float4 na 16 — zaslon se je napolnil z barvnimi bloki), [[point_size]] v senčilniku fragmentov ni bil berljiv kot varying (vsak odtis je bil kvadraten), soobstoj dveh kodirnikov izrisa na enem ukaznem medpomnilniku (vse črno) in manjkajoči začetni odtis, ki je pustil prvi milimeter hitrih potez neviden.
Šest korakov, ne ena prenova
Načrt selitve je obsegal šest korakov, ki jih je mogoče dobaviti neodvisno: izrisovalnik brez povezave, ki opravi primerjavo slikovnih pik; lupina prikaza brez vizualne spremembe; poteza v živo na GPU; izbira na GPU; spremembe, ki zapisujejo teksturo neposredno; vektorsko ponovno izrisovanje pri močnem zumiranju (vključeno v drugi korak, ker je »nič vizualne spremembe« to zahtevalo). Vsak korak se je končal s človekom — ne s simulatorjem, ne s primerjavo posnetkov zaslona —, ki je na iPadu Pro na mizi pisal, brisal, zumiral in vlekel.
Naprava je ujela tri hrošče, ki so se izmuznili vsem avtomatiziranim preverjanjem. Pri zumu nad 100 % so bile poteze izrisane dvakrat — mehka tekstura spodaj, ostri spritei zgoraj —, kar je bilo videti kot šibko zamegljevanje, ki ga je človek opazil v nekaj sekundah. Potrditev poteze je za en okvir utripala, ker se je stari prekrivni sloj s prelivom ugašal neusklajeno s posodobitvijo teksture. In pri zumu nad 170 % so vsi zapiski izginili: pravokotnik za izločanje vidnosti je uporabljal contentOffset v svojem skaliranem koordinatnem prostoru, zato se je med zumiranjem odmikal od potez. Vsak od treh je bil popravljen z eno vrstico v eni funkciji in nobenega od njih ni bilo v nobenem preizkusu, ki bi ga lahko napisali vnaprej, ker nismo vedeli, da jih moramo iskati. Pri potrošniški aplikaciji, usmerjeni v UI, je zato človek v zanki: našteti ni mogoče vseh načinov, kako se »občutek« pokvari.
Kakšno je delo s Kimi K3
Najprej je hiter. Zanka »pogovori, piši, gradi, namesti, poglej« je trajala nekaj minut, model, ki hitro odgovarja, pa spremeni, koliko zank si lahko privoščiš v dnevu.
Drugič, ne prekomerno inženjira. Ta zbirka kode deluje po izrecnih hišnih pravilih — brez gradnje za združljivost nazaj pred izidom, zapletenost šele takrat, ko jo naprava dokaže kot nujno — in K3 jih upošteva brez opominjanja. Ni dodajal prostorskih indeksov »za pozneje«, ni ovijal vsakega klica v defenzivne preverbe, ni špekulativno abstrahiral. Delo z njim prek pozivov je kot delo s strokovnim sodelavcem, ki je prebral hišna pravila in jim resnično verjame.
Tretjič, dajte mu orodja in jih z veseljem uporabi. Povezali smo pripomočke za slike — pogled, obrez na slikovno območje, sprememba velikosti — in model je začel proaktivno obrezovati lasten izhod izrisovalnika, da je diagnosticiral pet zgornjih hroščev. Ko ima orodje, se spomni, da mora pogledati.
Druga polovica: K3 je napisal večino hroščev v tej zgodbi, vključno s tistim v koordinatnem prostoru, ki je zapiske pustil izginiti. Njegove meje so resnične. Delo ni bilo varno zato, ker bi imel model prav; varno je bilo zato, ker je preizkus ujel odklone pri izrisu, človek pa občutek. In vendar ga iz dneva v dan nisem mogel zanesljivo ločiti od vodilnih zaprtih modelov, ki jih prav tako uporabljamo — sistemov razreda Opus. V nekaterih pogledih je bil očitno boljši: hitrejši in veliko manj nagnjen k napihnitvi zbirke kode z defenzivno zasnovo.
Kako želimo graditi od zdaj naprej
Končali smo z agentskim razvojem po velikih specifikacijah — s slogom, kjer modelu date veliko specifikacijo in sprejmete, kar pristane. Način odpovedi ni slaba koda; to je koda, ki je nihče ne razume.
Kar je tu delovalo in kar bomo obdržali: majhni koraki, vsak razpravljen, preden se začne, vsak razumljen s strani človeka, preden je zgrajen, vsak preverjen na napravi, na kateri bo živel. Naloga modela je, da je hiter, natančen in iskren o negotovosti. Naloga človeka je presoja, okus in preverjanje od konca do konca — še posebej za potrošniško programje, usmerjeno v UI, kjer specifikacija ne more opisati, kakšen je pravi občutek. Izkazalo se je, da je Kimi K3 na Fireworks dobro prilagojen ravno za to zanko: dovolj hiter, da zanka ostane tesna, dovolj pameten, da koraki ostanejo majhni in čisti.
Valovanja ni več, cevovod je ena ideja namesto dveh, postopek, ki nas je pripeljal tja, pa ostaja.