Lulucat

Membina Semula Saluran Paip Pemaparan Lulucat Notes dengan Kimi K3

Gaoge ZhangGaoge Zhang

Saluran paip jubin Core Graphics kami menjadi saluran paip point-sprite Metal dalam enam langkah kecil, setiap satunya disahkan pada iPad sebenar. Kod ditulis melalui pengaturcaraan berpasangan dengan Kimi K3 di Fireworks.

Dikemas kini

Minggu lalu, apabila kami menyeret pilihan laso merentasi Lulucat Notes, satu gelombang yang kelihatan muncul: sesetengah jubin skrin menunjukkan pilihan pada kedudukan baharunya, manakala yang lain masih menunjukkan kedudukan lama, dalam bingkai yang sama. Kami menggantikan keseluruhan saluran paip pemaparan — bitmap Core Graphics bersama CATiledLayer — dengan Metal dalam enam langkah kecil, setiap satunya disahkan pada iPad sebenar sebelum langkah berikutnya bermula.

Kod ditulis melalui pengaturcaraan berpasangan dengan Kimi K3, model terbuka Moonshot yang berjalan di Fireworks. Manusia memandu, membuat keputusan dan menguji; model menulis hampir setiap baris.

Saluran paip

Saluran paip lama mempunyai dua jenis lukisan yang perlahan-lahan berpisah: sapuan dibakar ke dalam bitmap untuk paparan kos rendah, kemudian dilukis semula sebagai vektor setiap kali jubin memerlukan lebih banyak perincian. Saluran paip baharu mempunyai tepat satu idea: segala yang menyerupai dakwat ialah point sprite. Sapuan pen, sapuan penyerlah dan dab pemadam ialah vertex 32 bait yang sama — kedudukan, diameter, warna — dilukis oleh pasangan shader yang sama sebagai bulatan yang diraster GPU, dijarakkan satu point sepanjang panjang lengkung sapuan.

Tiga panel sapuan melengkung yang sama: titik sentuhan input, cop bulat yang dijarakkan sepanjang lengkung mengikut panjang lengkung, dan sapuan padu yang dikomposit.

Dengan jarak satu point, satu rantai bulatan menyimpang daripada kapsul yang sempurna secara matematik kira-kira 0.075 point — satu perlima piksel pada ketumpatan kanvas kami. Sebagai pertukaran, tiga alat bergabung menjadi satu laluan kod, dan GPU melakukan perkara yang paling dikuasainya.

Di sekeliling idea itu, seni binanya mudah. Dakwat yang telah dikomit berada dalam satu tekstur 4096². UIScrollView kekal, tetapi diturunkan menjadi enjin gerak isyarat semata-mata: contentOffset dan zoomScale-nya membekalkan uniform viewport pada setiap bingkai, jadi panning dan zooming tidak menulis apa-apa. Setiap bingkai mempunyai lima operasi lukisan:

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

Suntingan menulis terus ke tekstur. Mengomit satu sapuan menambah copnya. Memadam, menghapus, mengalihkan dan membuat asal memainkan semula kawasan yang terjejas di belakang segi empat tepat scissor: kosongkan kawasan itu, lukis semula sapuan yang bersilang dengannya, selesai. Pemadaman separa mengekalkan semantik pemilikan daripada catatan terdahulu — sesuatu pemadaman dimiliki oleh sapuan yang dakwatnya dikeluarkan — dengan melukis setiap sapuan yang dipadam ke dalam tekstur scratch, menolak laluan pemadamannya sendiri menggunakan pengadunan destination-out (), kemudian mengompositkan semula hasilnya. Pengasingan scratch menghalang pemadam daripada menembusi kertas atau sapuan bersebelahan.

Pilihan yang memulakan semua ini kini turut dilukis sebagai point sprite. Menyeretnya mengemas kini satu offset uniform. Sifar tulisan tekstur, sifar pembatalan jubin — gelombang itu hilang secara struktur, bukan sekadar dikurangkan.

Penjaga pintu: satu diff piksel

Kami tidak memadam perender Core Graphics. Kami menurunkannya menjadi pelaksanaan rujukan luar talian, dan setiap perubahan Metal mesti lulus perbandingan piksel dengannya menggunakan data sapuan sebenar yang ditangkap pada peranti. Kriteria penerimaan bukanlah “piksel yang sama” — dua rasterizer yang betul sememangnya boleh berbeza beberapa aras kelabu di sepanjang tepi antialias. Gerbangnya bersifat struktural: tiada dakwat hilang, tiada offset, tiada hanyutan warna dan tiada perbezaan besar di mana-mana tempat yang jauh daripada dakwat.

Tiga keratan nota tulisan tangan yang sama: dirender oleh Core Graphics, dirender oleh point sprite Metal, dan perbezaan pikselnya diperbesarkan enam kali, menunjukkan hanya rangka samar sepanjang tepi sapuan.

Harness ini menangkap empat daripada lima pepijat yang kami temui ketika membina perender luar talian, semuanya didiagnosis dengan memotong output hingga ke tahap piksel: ketidakpadanan stride struct Swift/Metal (28 bait berbanding 32, kerana Metal menjajarkan float4 kepada 16 — skrin dipenuhi blok warna), [[point_size]] yang tidak boleh dibaca sebagai varying dalam fragment shader (setiap cop menjadi segi empat sama), dua render encoder yang wujud serentak pada satu command buffer (semuanya hitam), dan cop permulaan yang hilang sehingga milimeter pertama sapuan pantas tidak kelihatan.

Enam langkah, bukan satu penulisan semula

Pelan penghijrahan itu terdiri daripada enam langkah yang boleh dihantar secara berasingan: perender luar talian yang lulus diff piksel; cengkerang paparan tanpa perubahan visual; sapuan langsung pada GPU; pilihan pada GPU; mutasi yang menulis terus ke tekstur; lukisan semula vektor pada zum tinggi (dilipat ke dalam langkah kedua kerana “sifar perubahan visual” menuntutnya). Setiap langkah berakhir dengan seorang manusia — bukan simulator, bukan diff tangkap layar — menulis, memadam, menzum dan menyeret pada iPad Pro di atas meja.

Peranti menangkap tiga pepijat yang terlepas daripada setiap semakan automatik. Di atas zum 100%, sapuan dilukis dua kali — tekstur lembut di bawah, sprite tajam di atas — lalu kelihatan sebagai kabur samar yang disedari manusia dalam beberapa saat. Mengomit satu sapuan menyebabkan kelipan selama satu bingkai kerana tindanan lama melakukan cross-fade keluar tanpa selari dengan kemas kini tekstur. Dan di atas zum 170%, setiap nota hilang: segi empat tepat visibility-culling menggunakan contentOffset dalam ruang koordinat yang diskalakan, lalu hanyut menjauhi sapuan apabila anda menzum. Ketiga-tiganya ialah pembaikan satu baris dalam satu fungsi, dan tiada satu pun dapat dikesan oleh ujian yang boleh kami tulis lebih awal, kerana kami tidak tahu untuk mencarinya. Bagi aplikasi pengguna UI-first, itulah sebabnya manusia kekal dalam gelung.

Bagaimana rasanya bekerja dengan Kimi K3

Pantas, pertama sekali. Gelung “berbincang, menulis, membina, memasang, melihat” berjalan dalam beberapa minit, dan model yang menjawab dengan pantas mengubah jumlah gelung yang mampu anda jalankan dalam sehari.

Kedua, ia tidak mereka bentuk secara berlebihan. Pangkalan kod ini berjalan berdasarkan peraturan dalaman yang jelas — tiada perancah keserasian ke belakang sebelum pelancaran, kerumitan hanya apabila peranti membuktikan bahawa ia diperlukan — dan K3 mematuhinya tanpa perlu diingatkan. Ia tidak menambah indeks ruang “untuk kemudian”, tidak membungkus setiap panggilan dalam semakan defensif dan tidak membuat abstraksi spekulatif. Memberinya prompt terasa seperti bekerja dengan rakan sekerja yang cekap, yang telah membaca peraturan dalaman dan benar-benar mempercayainya.

Ketiga, berikan ia alat dan ia menggunakannya dengan penuh semangat. Kami menyambungkan utiliti imej — lihat, potong ke kawasan piksel, ubah saiz — dan model mula memotong output perendernya sendiri secara proaktif untuk mendiagnosis lima pepijat harness di atas. Memiliki alat itu mengingatkannya untuk melihat.

Separuh yang lain: K3 menulis kebanyakan pepijat dalam cerita ini, termasuk pepijat ruang koordinat yang menyebabkan nota hilang. Hadnya nyata. Apa yang menjadikan kerja ini selamat bukanlah model yang sentiasa betul; ia ialah harness yang menangkap hanyutan pemaparan dan manusia yang menilai pengalaman penggunaan. Namun begitu, dari hari ke hari, saya tidak dapat membezakannya dengan pasti daripada model frontier tertutup yang turut kami gunakan — sistem kelas Opus. Dalam beberapa aspek ia jelas lebih baik: lebih pantas dan jauh kurang cenderung memenuhi pangkalan kod dengan reka bentuk defensif.

Cara kami mahu membina mulai sekarang

Kami sudah selesai dengan pembangunan agentic berasaskan spesifikasi besar — gaya apabila anda menyerahkan spesifikasi besar kepada model dan menerima apa sahaja yang dihasilkannya. Mod kegagalannya bukan kod yang buruk; ia kod yang tidak difahami sesiapa.

Apa yang berkesan di sini, dan yang akan kami kekalkan: langkah-langkah kecil, setiap satunya dibincangkan sebelum bermula, difahami oleh manusia sebelum dibina, dan disahkan pada peranti tempat ia akan digunakan. Tugas model ialah menjadi pantas, tepat dan jujur tentang ketidakpastian. Tugas manusia ialah pertimbangan, citarasa dan pengesahan e2e — terutamanya untuk perisian pengguna UI-first, apabila spesifikasi tidak dapat menerangkan bagaimana rasa “betul” itu. Kimi K3 di Fireworks ternyata sesuai untuk gelung ini: cukup pantas untuk mengekalkan gelung yang rapat, cukup pintar untuk mengekalkan langkah yang kecil dan bersih.

Gelombang itu telah hilang, saluran paip itu kini satu idea dan bukan dua, dan proses yang membawa kami ke situ akan kekal.