# Snapshot Freeze Analysis — Çizim Bitimindeki Takılma ## Problem Fırça darbesi bittiğinde (pen-up), UI thread kısa süreli donuyor. Bu takılma snapshot (geri alım) mekanizmasının UI thread'de yaptığı büyük bellek tahsislerinden kaynaklanıyor. ## Kök Neden `end_stroke()` ve `begin_stroke()` fonksiyonları UI thread'de çalışır ve her biri ~33MB'lik `layer.pixels.clone()` çağrısı yapar. 4K canvas'ta (3840×2160×4 = ~33MB) bu kopyalamalar allocate+copy gerektirir. ## Suçlu İşlemler | # | İşlem | Dosya:Satır | Boyut | Etki | |---|-------|-------------|-------|------| | 1 | `layer.pixels.clone()` — before snapshot | `stroke_cache.rs:125` | ~33MB alloc+copy | Yüksek | | 2 | `layer.pixels.clone()` — after snapshot | `stroke_cache.rs:159` | ~33MB alloc+copy | **En yüksek** | | 3 | `active_stroke_mask.fill(0)` | `stroke_cache.rs:224` | ~8MB sıfırlama | Orta | | 4 | `raw_pixel_backup.remove()` — drop | `stroke_cache.rs:219` | ~33MB deallocation | Orta | | 5 | `apply_effects_and_sync_tiles()` | `partial_composite.rs:206-268` | ~99MB+ (efektli katmanlarda) | Yüksek | ## Akış Diyagramı ``` begin_stroke() end_stroke() ───────────── ───────────── 1. mask.fill(0) [pooled, ~8MB] 1. before = stroke_before.take() 2. before = layer.pixels.clone() [33MB] 2. after = layer.pixels.clone() [33MB] ← DONMA 3. stroke_before = Some(before) 3. thread::spawn(move || { ... }) 4. rebuild_below_cache() 4. mask.fill(0) [pooled] 5. raw_pixel_backup.remove() 6. cached_selection_mask = None ``` ## Mevcut Havuz Mekanizmaları (İyi) | Buffer | Boyut | Havuzda mı? | Not | |--------|-------|-------------|-----| | `active_stroke_mask` | ~8MB | Evet | `begin_stroke`'da fill(0), stroke arası korunur | | `composite_scratch` | ~33MB | Evet | Render sırasında kullanılır, serbest bırakılmaz | | `below_cache` | ~33MB | Evet | Stroke arası korunur, `below_cache_dirty` ile yönetilir | ## Havuzlanmayan Buffer'lar (Kötü) | Buffer | Boyut | Neden havuzlanmıyor? | |--------|-------|----------------------| | `stroke_before` (before snapshot) | ~33MB | Her `begin_stroke`'da `clone()`, her `end_stroke`'da consume edilir | | `stroke_after` (after snapshot) | ~33MB | Her `end_stroke`'da `clone()`, background thread'e move edilir | ## Kritik Gözlem `end_stroke()` çağrıldığında fırça darbesi bitmiştir. Bir sonraki `begin_stroke()`'a kadar `layer.pixels`'e hiçbir yazılmaz. Bu, background thread'in raw pointer ile okuma yapabileceği anlamına gelir — unsafe ama güvenli. ## Optimize Edilmiş Akış (Hedef) ``` begin_stroke() end_stroke() ───────────── ───────────── 1. mask.fill(0) [pooled] 1. before = pooled buffer'dan take 2. copy layer.pixels → pooled buf 2. copy layer.pixels → pooled after buf 3. stroke_before = Some(pooled buf) 3. both buflar → background thread 4. rebuild_below_cache() 4. mask.fill(0) [pooled] 5. background thread: sub-rect çıkar, buffer'ları geri gönder ``` ## Dosyalar - `hcie-engine-api/src/stroke_cache.rs` — begin_stroke, end_stroke - `hcie-engine-api/src/lib.rs` — Engine struct tanımı - `hcie-document/src/lib.rs` — push_draw_snapshot, push_draw_snapshot_subrect - `hcie-protocol/src/lib.rs` — Layer struct tanımı