Lulucat

Přestavba vykreslovací pipeline Lulucat Notes pomocí Kimi K3

Gaoge ZhangGaoge Zhang

Naše dlaždicová pipeline Core Graphics se v šesti malých krocích změnila v Metal pipeline bodových spritů, přičemž každý krok jsme ověřili na skutečném iPadu. Kód vznikal párovým programováním s Kimi K3 na Fireworks.

Aktualizováno

Minulý týden vytvořilo tažení výběru lasem napříč Lulucat Notes viditelnou vlnu: některé dlaždice obrazovky ukazovaly výběr na nové pozici, zatímco jiné ve stejném snímku stále ukazovaly starou. Celou vykreslovací pipeline — bitmapu Core Graphics plus CATiledLayer — jsme nahradili technologií Metal v šesti malých krocích; každý jsme ověřili na skutečném iPadu, než začal další.

Kód vznikal párovým programováním s Kimi K3, otevřeným modelem od Moonshot běžícím na Fireworks. Člověk určoval směr, rozhodoval a testoval; model napsal téměř každý řádek.

Pipeline

Stará pipeline měla dva druhy vykreslování, které se od sebe postupně vzdalovaly: tahy se kvůli levnému zobrazení zapečely do bitmapy a znovu se vykreslovaly jako vektory, kdykoli dlaždice potřebovaly více detailu. Nová pipeline má jedinou myšlenku: všechno, co připomíná inkoust, je bodový sprite. Tah pera, tah zvýrazňovače i otisk gumy jsou stejný 32bajtový vrchol — pozice, průměr, barva — vykreslovaný stejnou dvojicí shaderů jako kružnice rasterizované GPU, rozmístěné s rozestupem jednoho bodu podél délky oblouku tahu.

Tři panely stejného zakřiveného tahu: vstupní dotykové body, kruhové otisky rozmístěné podél křivky podle délky oblouku a složený plný tah.

Při rozestupu jednoho bodu se řetěz kružnic od matematicky dokonalé kapsuly odchyluje asi o 0.075 bodu — při hustotě našeho plátna je to pětina pixelu. Výměnou se tři nástroje smrsknou do jediné cesty a GPU dělá to, v čem je nejlepší.

Kolem této myšlenky je architektura jednoduchá. Potvrzený inkoust žije v jediné textuře 4096². UIScrollView zůstává, ale je odsunut do role čistého enginu gest: jeho contentOffset a zoomScale každý snímek napájí uniformní proměnnou viewportu, takže posouvání ani přibližování nic nezapisuje. Každý snímek tvoří pět vykreslení:

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

Editace zapisují texturu přímo. Potvrzení tahu přidá jeho otisky. Mazání, odstraňování, přesouvání a undo znovu přehrají zasaženou oblast za obdélníkem scissor testu: oblast vyčistit, znovu vykreslit tahy, které ji protínají, hotovo. Částečné mazání zachovává sémantiku vlastnictví z předchozího článku — mazání patří tahu, z něhož odstraňuje inkoust — tím, že každý mazaný tah vykreslí do pomocné textury, odečte jeho vlastní mazací cesty pomocí blendování destination-out () a výsledek složí zpět. Izolace pomocné textury brání gumě prokousat se papírem nebo sousedními tahy.

Výběr, který to celé odstartoval, se nyní kreslí také jako bodové sprity. Tažení mění jediný uniformní offset. Nula zápisů do textury, nula invalidací dlaždic — vlna je pryč strukturálně, ne jen zmírněná.

Strážce: pixelový diff

Vykreslovač Core Graphics jsme nesmazali. Odsunuli jsme ho do role offline referenční implementace a každá změna v implementaci Metal musí na skutečných datech tahů zachycených na zařízení projít pixelovým porovnáním s ním. Kritériem přijetí nejsou „identické pixely“ — dva správné rasterizéry se po vyhlazených hranách oprávněně liší o několik odstínů šedi. Brána je strukturální: žádný chybějící inkoust, žádný posun, žádný barevný posun a žádný velký rozdíl kdekoli mimo inkoust.

Tři výřezy stejných rukou psaných poznámek: vykreslení Core Graphics, vykreslení Metal pomocí bodových spritů a jejich pixelový rozdíl zesílený šestkrát, který ukazuje jen slabé obrysy podél hran tahů.

Tento harness odhalil čtyři z pěti chyb, na které jsme narazili při stavbě offline vykreslovače. Všechny jsme diagnostikovali ořezáním výstupu na úroveň pixelů: nesoulad stride struktury Swift/Metal (28 bajtů místo 32, protože Metal zarovnává float4 na 16 — obrazovku vyplnily barevné bloky), [[point_size]] nešlo ve fragment shaderu přečíst jako varying (každý otisk vyšel čtvercový), dva renderovací enkodéry existovaly současně v jednom command bufferu (všechno bylo černé) a chyběl počáteční otisk, kvůli čemuž byl první milimetr rychlých tahů neviditelný.

Šest kroků, ne jeden přepis

Plán migrace tvořilo šest samostatně vydatelných kroků: offline vykreslovač, který prochází pixelovým diffem; vrstva zobrazení bez vizuální změny; živý tah na GPU; výběr na GPU; mutace zapisující texturu přímo; vektorové překreslování při velkém přiblížení (sloučené do druhého kroku, protože „bez vizuální změny“ to vyžadovalo). Každý krok skončil tím, že člověk na iPadu Pro na stole psal, mazal, přibližoval a táhl — ne simulátor, ne porovnání screenshotů.

Zařízení odhalilo tři chyby, které každá automatizovaná kontrola přehlédla. Při zvětšení nad 100% se tahy vykreslovaly dvakrát — měkká textura pod nimi, ostré sprity nad nimi — což se projevilo jako slabé rozmazání, kterého si člověk všiml během několika sekund. Potvrzení tahu způsobilo bliknutí na jeden snímek, protože starý overlay se při prolínání skryl mimo synchronizaci s aktualizací textury. A při zvětšení nad 170% zmizely všechny poznámky: obdélník pro ořezávání viditelnosti používal contentOffset ve škálovaném souřadnicovém prostoru, takže se při zvětšování vzdaloval od tahů. Všechny tři problémy šly opravit jediným řádkem v jediné funkci a žádný z nich neexistoval v žádném testu, který jsme mohli předem napsat, protože jsme nevěděli, že je máme hledat. U spotřebitelské aplikace s UI na prvním místě proto člověk zůstává v procesu.

Jak se s Kimi K3 pracuje

Především je rychlý. Cyklus „probrat, napsat, sestavit, nainstalovat, podívat se“ trval minuty a model, který odpovídá rychle, mění počet cyklů, které si za den můžete dovolit.

Za druhé nepřekomplikovává. Tento projekt funguje podle explicitních interních pravidel — před vydáním žádné lešení zpětné kompatibility, složitost jen tehdy, když zařízení prokáže, že je nutná — a K3 se jich drží bez připomínání. Nepřidal prostorové indexy „na později“, neobalil každé volání obrannými kontrolami a nevymýšlel abstrakce pro jistotu. Zadávání promptů působí jako práce se schopným kolegou, který si přečetl interní pravidla a skutečně jim věří.

Za třetí mu dejte nástroje a bude je dychtivě používat. Zapojili jsme obrazové utility — prohlížení, ořez na pixelovou oblast a změnu velikosti — a model začal z vlastní iniciativy ořezávat výstup svého vykreslovače, aby diagnostikoval pět chyb harnessu popsaných výše. Samotná existence nástroje mu připomněla, že se má dívat.

Druhá strana: K3 napsal většinu chyb v tomto příběhu, včetně té v souřadnicovém prostoru, kvůli níž zmizely poznámky. Jeho limity jsou skutečné. Bezpečnou práci nezajistilo to, že by model měl vždy pravdu; zajistil ji harness, který zachytil posuny ve vykreslování, a člověk, který zachytil pocit z používání. A přesto jsem ho v každodenní práci nedokázal spolehlivě rozeznat od špičkových uzavřených modelů, které také používáme — systémů třídy Opus. V některých ohledech byl zjevně lepší: byl rychlejší a mnohem méně inklinoval k plnění kódové základny obranným návrhem.

Jak chceme odteď stavět

Skončili jsme s agentním vývojem podle velkých specifikací — stylem, kdy modelu předáte obsáhlou specifikaci a přijmete cokoli, co vznikne. Selháním není špatný kód; jsou jím kódy, kterým nikdo nerozumí.

To, co zde fungovalo a co si ponecháme: malé kroky, každý předem prodiskutovaný, člověkem pochopený ještě před implementací a ověřený na zařízení, na němž bude žít. Úkolem modelu je být rychlý, přesný a upřímný ohledně nejistoty. Úkolem člověka je úsudek, vkus a e2e ověřování — zvlášť u spotřebitelského softwaru s UI na prvním místě, kde specifikace nedokáže popsat, jaký má být správný pocit z používání. Kimi K3 na Fireworks se ukázal jako model přesně pro tento cyklus: dost rychlý na to, aby cyklus zůstal těsný, a dost chytrý na to, aby kroky zůstaly malé a čisté.

Vlna je pryč, pipeline je jedna myšlenka místo dvou a proces, který nás k tomu dovedl, zůstává.