Prestasi Alat Kapur: Daripada Pass Skrin Penuh kepada Segi Empat Tepat Scissor
Alat kapur dalam Lulucat Notes menjadi perlahan di kawasan tulisan tangan yang padat. Kekangan prestasi utamanya bukan 3,571 sampel input, tetapi 70 scratch pass skrin penuh bagi setiap bingkai. Cache resolusi rendah yang ditolak dan segi empat tepat scissor bagi setiap sapuan menceritakan selebihnya.

Kapur merah dan biru pada binaan peranti untuk pengesahan akhir, 155 sapuan pada zum 255%.
Alat kapur dalam Lulucat Notes mempunyai masalah prestasi yang khusus: menulis di kawasan kosong terasa lancar, tetapi apabila masuk ke kawasan yang sudah dipenuhi sapuan kapur, hujung pen mula ketinggalan. Terus menulis di kawasan yang sama juga memperlahankan panning kanvas secara beransur-ansur.
Satu halaman tulisan tangan biasa sudah cukup untuk mencetuskannya: zum 300%, 70 sapuan kapur yang kelihatan di kawasan setempat, berjumlah 3,571 titik sampel input. Kawasan kosong kekal lancar; hanya kawasan tempat sapuan tertumpu menjadi perlahan.
Selepas pembaikan, halaman yang sama boleh terus menerima tulisan baharu pada zum 255%, dan sapuan sedia ada mengekalkan kejelasan penuh semasa pen-down dan panning.

Tangkapan skrin peranti untuk pengesahan akhir, 155 sapuan semuanya. Pada tahap zum ini, pen-down dan panning tidak menukar kejelasan sapuan sedia ada buat sementara waktu.
Mengapa kapur memerlukan tekstur scratch
Pen biasa boleh mengomposit setiap cop bulat terus ke tekstur dakwat dengan pengadunan source-over. Kapur menambah lapisan grain-gating: perender mula-mula mengumpulkan body coverage dan depth bagi seluruh sapuan, kemudian menggunakan tekstur grain tetap untuk menentukan kedudukan yang menerima habuk kapur, dan akhirnya mengompositkan hasilnya ke atas dakwat sedia ada.
Tekstur scratch ini mengasingkan satu sapuan kapur. Pengasingan penting kerana cop dalam sapuan yang sama banyak bertindih; jika setiap cop diberi grain gate secara berasingan, garis tengah sapuan akan mengumpulkan warna berulang dan liang kapur akan beralih mengikut ketumpatan pensampelan.
Pada tahap zum tinggi, Lulucat Notes melukis semula sapuan vektor yang kelihatan dalam viewport semasa. Pelaksanaan lama melakukan langkah berikut bagi setiap sapuan kapur yang kelihatan:
- Menamatkan main render encoder;
- Mengosongkan tekstur scratch;
- Melukis satu sapuan kapur ini ke dalam scratch;
- Membuka semula main render encoder;
- Mengompositkan scratch kembali ke drawable dengan segi tiga skrin penuh.
Semantik satu sapuan adalah betul, tetapi skop kerja jauh lebih besar. Drawable iPad berukuran 2732×2048 — kira-kira 5.6 juta piksel. Setiap sapuan kapur mencetuskan satu scratch pass dan satu komposit skrin penuh. Tujuh puluh sapuan kapur bermaksud kira-kira 141 render encoder dan 70 komposit skrin penuh.
Biarkan bilangan sapuan kapur yang kelihatan ialah
Setiap sapuan kapur juga membawa overhed render-pass tetap, jadi kos itu turut meningkat secara linear dengan
Pengukuran dibuat pada iPad Pro 12.9 inci (generasi ke-5, M1) yang menjalankan iPadOS 18.6.2. Kami membandingkan timestamp GPU daripada viewport yang sama sebelum dan selepas perubahan, menggunakan timestamp command-buffer dalam binaan peranti Debug yang sama pada iPad ini — selepas ini dipanggil LucasPad. Julat di bawah ialah turun naik biasa daripada log berbilang bingkai, bukan janji kadar bingkai untuk versi yang akan diedarkan. Dengan 70 sapuan kapur yang kelihatan, satu bingkai biasanya memerlukan 52–60 ms masa GPU; di kawasan dengan kira-kira 120 sapuan, masa GPU meningkat kepada 77–80 ms.
Dianggarkan berdasarkan luas segi empat tepat skrin penuh bagi pass scratch dan komposit, skop kerja teori setiap bingkai meningkat daripada kira-kira 783 juta piksel kepada 1.34 bilion piksel. Angka ini ialah jumlah luas segi empat tepat dan tidak bersamaan dengan bilangan seruan fragment, bait bacaan/tulisan memori video atau pembilang perkakasan GPU. Fast clear Metal, attachment load/store dan pertukaran pass kekal di bawah kawalan GPU dan pemacu.
Ini juga menerangkan mengapa kawasan kosong kekal lancar. Visibility culling melangkau sapuan di luar viewport;
Jawapan yang salah pada 0.85 ms
Aplikasi sudah mempunyai tekstur dakwat seluruh halaman yang dibakar pada dua piksel setiap titik. Kami cuba memaparkan tekstur ini secara langsung semasa menulis, panning dan menzum, dengan hanya sapuan Apple Pencil semasa dikekalkan sebagai vektor langsung; selepas interaksi tamat, satu bingkai tambahan akan melukis semula hasil vektor resolusi tinggi.
Pendekatan ini berprestasi sangat baik. Di kawasan padat yang sama pada zum 300%, masa GPU turun kepada 0.84–0.85 ms dan tidak lagi meningkat mengikut bilangan sapuan kapur sedia ada.
Masalah pada peranti sebenar juga sama jelas. Pada zum 300%, kira-kira enam piksel skrin bagi setiap titik diperlukan, tetapi cache hanya menyediakan dua. Sebaik sahaja Apple Pencil menyentuh skrin, semua sapuan sedia ada bertukar menjadi imej lembut beresolusi rendah; apabila Pencil diangkat, ia kembali kepada kejelasan penuh.
Penguji berkata: “Apabila saya menulis, seluruh kanvas menjadi kabur. Ia menjadi jelas semula apabila saya melepaskannya.”
Pengoptimuman itu dibuang. 0.85 ms ialah hasil terendah yang diukur, tetapi ia bukan alat kapur yang boleh diterima. Sapuan sedia ada ialah sebahagian daripada maklum balas penulisan; kejelasannya tidak boleh berubah apabila pen-down.
Mengehadkan setiap sapuan kapur kepada segi empat tepatnya sendiri
Pembaikan akhir mengekalkan scratch setiap sapuan dan pengompositan setiap sapuan, lalu hanya mengurangkan skop kerja pikselnya. Setiap sapuan sudah mempunyai bounding box kanvas yang terhasil daripada gabungan semua jejari copnya. Perender menukar bounding box ini kepada koordinat drawable viewport semasa dan menambah padding dua piksel untuk margin antialiasing.
Segi empat tepat scissor yang sama digunakan untuk tiga perkara: mengosongkan scratch, melukis sapuan dan mengompositkan hasilnya kembali ke permukaan utama.
let rect = displayScissorRect(for: stroke.bounds, viewport: viewport)
scratchEncoder.setScissorRect(rect)
clearScratchExplicitly()
drawStrokeIntoScratch(stroke)
mainEncoder.setScissorRect(rect)
compositeChalkFromScratch(stroke)
mainEncoder.setScissorRect(fullDrawable)
Logik yang sama juga digunakan untuk baking dan partial replay pada tekstur dakwat 4096², supaya paparan zum tinggi dan lapisan dakwat yang telah dimuktamadkan tidak menghasilkan dua tingkah laku kapur yang berbeza.
Dua perincian mudah terlepas di sini.
Pertama, loadAction = .clear bagi render pass berlaku semasa peringkat attachment load dan tidak dihadkan oleh rasterization scissor. Jika terus digunakan, ia masih akan mengosongkan seluruh tekstur scratch. Pass tetap menggunakan .dontCare, kemudian melukis clear_fragment dalam scissor. Segi empat tepat ini kemudiannya ditulis sepenuhnya, dan komposit hanya membaca segi empat tepat yang sama, jadi kandungan attachment lama tidak perlu dimuatkan.
Kedua, selepas komposit setiap sapuan kapur selesai, scissor luar mesti dipulihkan. Jika baris pemulihan state ini ditinggalkan, pen, imej atau pilihan seterusnya akan terus dipotong oleh sempadan sapuan kapur sebelumnya, lalu kelihatan seperti sapuan atau imej yang hilang.
Grain kapur masih mengambil sampel daripada koordinat kanvas mutlak, bukan UV setempat dalam segi empat tepat. Mengalihkan scissor hanya mengubah piksel yang diproses GPU; ia tidak mengubah lokasi tekstur grain yang dibaca oleh setiap piksel. Oleh itu, segi empat tepat bersebelahan tidak menghasilkan jahitan tekstur, dan menyeret kanvas tidak menyebabkan grain hanyut.
Jika hanya beban kerja piksel dipertimbangkan, skop kerja baharu hampir kepada
di mana
Mengapa sapuan kapur warna sama tidak dibatch
Kebanyakan sapuan kapur pada halaman berkongsi warna dan ketumpatan yang sama, dan memang menggoda untuk melukis berpuluh-puluh sapuan ke dalam scratch sekali gus lalu mengomposit hanya sekali. Ini akan mengurangkan render pass dengan lebih lanjut, tetapi mengubah semantik warna dan grain di kawasan yang bertindih.
Pertimbangkan kes yang sengaja dipermudahkan: dua sapuan berkongsi nilai grain-gate
manakala menggabungkan body dahulu lalu menggunakan satu gate menghasilkan
Perbezaannya ialah
Batch yang tepat memerlukan bukti bahawa piksel sapuan adalah saling tidak bersilang, atau memperuntukkan kawasan atlas bebas untuk setiap sapuan dan mengomposit dalam susunan asal. Penerimaan akhir pada peranti mengekalkan pendekatan scissor, jadi pusingan ini tidak memperkenalkan atlas atau kerumitan pengurusannya.
Daripada satu bilion piksel kembali kepada beberapa juta
Pengukuran peranti akhir:
| Senario | Sebelum pembaikan | Scissor tepat |
|---|---|---|
| 70 sapuan kapur kelihatan, zum 300%, menulis | GPU 52–60 ms | ≈ 9–10 ms |
| ≈ 121 sapuan kapur kelihatan, zum 300% | GPU 77–80 ms | 13.7–15.6 ms |
| Skop segi empat tepat teori setiap bingkai (scratch + komposit) | 783 M–1.34 B piksel | ≈ 1.7 M–3 M piksel |
Kami turut menyemak sempadan clipping menggunakan 3,452 sapuan dan 202,710 titik sampel daripada dokumen peranti. Pada zum 0.5×, 1×, 2×, 3×, 5× dan 8×, 186,408 kes viewport dihasilkan; setiap point sprite yang boleh menghasilkan coverage non-zero berada dalam scissor yang dikira. Semakan ini merangkumi tepi kanvas, tepi viewport dan pelbagai gabungan offset.
Kod akhir tidak bertukar kepada LOD beresolusi rendah berdasarkan keadaan interaksi. Tahap zum rendah masih memaparkan tekstur dakwat seluruh halaman; tahap zum tinggi masih melukis semula sapuan yang kelihatan sebagai vektor. Pada sisi ambang yang sama, pen-down dan panning tidak menggantikan sapuan sedia ada dengan tahap kejelasan yang berbeza. Semasa lukisan semula vektor zum tinggi, clear scratch dan komposit setiap sapuan kapur hanya meliputi bounding box skrin sendiri.
Masa GPU tidak menangkap kejelasan
Masalah prestasi GPU tidak semestinya berskala dengan kuantiti yang paling jelas dalam struktur data. Dalam kes ini, 3,571 titik input ialah suspek yang mudah; yang menentukan masa bingkai ialah kerja skrin penuh yang dicetuskan oleh setiap 70 sapuan kapur, bersama pertukaran render-passnya.
Semantik visual turut mengehadkan pengoptimuman yang tersedia. Scratch setiap sapuan, susunan pengompositan asal dan koordinat grain kanvas mutlak tidak boleh dibuang sesuka hati. Warna sama dan ketumpatan sama hanya bermakna parameternya sepadan — ia tidak membuktikan bahawa hasil yang bertindih boleh digabungkan.
Maklum balas peranti sebenar menolak versi dengan masa GPU terendah. Kata-kata “seluruh kanvas menjadi kabur” memberikan kekangan produk yang tidak dinyatakan oleh pengukuran semata-mata: apabila Apple Pencil menyentuh skrin, pengguna juga sedang melihat sapuan sedia ada.
Versi akhir tidak memperkenalkan lapisan cache interaksi baharu dan tidak mengurangkan kejelasan. Lukisan semula vektor zum tinggi hanya mengehadkan kerja setiap sapuan kapur kepada bounding box skrinnya sendiri. Selepas dimuatkan semula ke LucasPad, maklum balasnya ialah: “Nampak hebat.”