perf: sag tik efektlerinde ilk kullanim donmasi fix — shader warmup (gizli katman ile acilista derleme), .deb guncellendi
This commit is contained in:
BIN
dist/imajviewer_1.0.1_amd64.deb
vendored
BIN
dist/imajviewer_1.0.1_amd64.deb
vendored
Binary file not shown.
@@ -2,6 +2,7 @@
|
||||
|
||||
| Tarih | Dosya | Konu |
|
||||
|---|---|---|
|
||||
| 2026-08-06 | [rapor-sag-tik-donma-analizi.md](rapor-sag-tik-donma-analizi.md) | **Rapor** — sağ tuş efektlerinde donma analizi + slider UI değerlendirmesi |
|
||||
| 2026-08-03 | [zoom-clamp-mekanizmasi.md](zoom-clamp-mekanizmasi.md) | **Düzeltme** — `_vpSize` sıfırlama bug'ı: çift tıklama sonrası zoom ölümü/pan serbestliği |
|
||||
| 2026-08-03 | [performance_llvmpipe_issue.md](performance_llvmpipe_issue.md) | **Rapor** — llvmpipe donma analizi, kare hızı throttle, ölçüm sonuçları |
|
||||
| 2026-08-03 | [rapor-brightness.md](rapor-brightness.md) | **Rapor** — brightness efekti (Ctrl+Shift+sağ tık) + 4x5 matris birleştirme |
|
||||
|
||||
127
docs/rapor-sag-tik-donma-analizi.md
Normal file
127
docs/rapor-sag-tik-donma-analizi.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Rapor — Sağ Tuş Efektlerinde Donma Analizi ve Slider UI Değerlendirmesi
|
||||
|
||||
Tarih: 2026-08-06
|
||||
Durum: Analiz — B teşhisi netleşti, uygulama bekliyor
|
||||
|
||||
## 1. Semptom
|
||||
|
||||
- Sağ tuş ile kullanılan özelliklerde (contrast / saturation / brightness / rotate) uygulama **geçici olarak donuyor**.
|
||||
- Donma süresi **15sn - 1dk arası** (kısa değil, ciddi bir tek seferlik maliyet).
|
||||
- **Release build** kullanılıyor (.deb'den kurulu) → Dart JIT hipotezi elendi, donma release AOT'ta da var.
|
||||
- Donma **kalıcı değil**: kullanıcı "kapatayım mı" diye soruyor, "bekle" deyince **kendiliğinden düzeliyor**.
|
||||
- Donma sırasında yapılan işlemler (sağ tık + aşağı çekme, pan) donma açıldığında **yapılmış olarak görülüyor** — olaylar işleniyor, sadece görsel gecikiyor.
|
||||
- **Sadece çok pencereli (multi-instance) kullanımda oluyor**, tek pencerede olmuyor.
|
||||
- **Her pencere açılışında ilk efekt kullanımında bir kez oluyor; o donma açıldıktan sonra aynı pencerede tekrar olmuyor.**
|
||||
- Eskiden sadece rotate'te olurdu; şimdi **tüm efektlerde** oluyor, en çok rotate'te.
|
||||
- Donan pencere kilitli kalıyor; diğer pencereler normal çalışıyor. Task bardan kapatılabiliyor.
|
||||
- Kullanıcı şüphesi: klavye tuşlarının (Ctrl/Shift) basılı tutulmasıyla ilgili olabilir — "farklı bir şey olma ihtimali yüksek".
|
||||
- Soru: bu özellikleri **açılan kutu içinde slider UI** ile yapsak takılmalar çözülür mü?
|
||||
|
||||
## 2. Mevcut Mimari (lib/widgets/image_canvas.dart)
|
||||
|
||||
| Katman | Mekanizma |
|
||||
|---|---|
|
||||
| Mod seçimi | `_onPointerDown`: sağ tık + `_ctrlPressed`/`_shiftPressed` state'i → contrast (yok) / rotate (Shift) / saturation (Ctrl) / brightness (Ctrl+Shift) |
|
||||
| Modifier takibi | `HardwareKeyboard.instance.addHandler(_onHardwareKey)` — global, `return false` (yutmaz) |
|
||||
| Değer güncelleme | `_onPointerMove`: `dy * sensitivity` → `_markRenderDirty()` |
|
||||
| Render eşitleme | `_renderTick` ValueNotifier + `scheduleFrameCallback` — kare başına max 1 rebuild |
|
||||
| Image pipeline | `ResizeImage.resizeIfNeeded(cacheWidth: viewport×2)` + provider memoize (03 Ağu fix) |
|
||||
| Efekt | `ColorFiltered` (birleşik 4x5 matrix, cache'li) + rotate için `Transform` |
|
||||
| Varsayılan | `_isFilled = true` (BoxFit.cover), `FilterQuality.high` |
|
||||
|
||||
## 3. Kök Neden Analizi — Geliştirilmiş Teşhis (kullanıcı geri bildirimiyle)
|
||||
|
||||
Kullanıcının verdiği yeni bilgiler adayları eliyor:
|
||||
|
||||
| Gözlem | Hangi adayı eler/güçlendirir |
|
||||
|---|---|
|
||||
| Donma geçici, kendiliğinden düzeliyor | Aday A (kalıcı frame üretimi durması) **elendi** → B güçlendi |
|
||||
| Donma sırasında yapılan işlemler sonradan görülüyor | UI thread olayları **işliyor**; sadece render/raster geride → Aday A elendi, B güçlendi |
|
||||
| Sadece çok pencerede oluyor | Process başına soğuk kaynak → B güçlendi |
|
||||
| Her pencerede İLK efekt kullanımında bir kez, sonra tekrar olmuyor | **İlk kullanım maliyeti** — shader derleme / kaynak ısınması → B1 (aşağıda) |
|
||||
| Eskiden sadece rotate, şimdi tüm efektlerde | Efekt başına ilk-kullanım maliyeti → B1 |
|
||||
| En çok rotate'te | Rotate en pahalı (Transform + ColorFiltered birlikte) → B |
|
||||
|
||||
### Teşhis: B1 — İlk Kullanım Render Maliyeti (shader derleme + kaynak ısınması) ⭐⭐
|
||||
|
||||
Semptom setinin tamamını açıklayan tek hipotez:
|
||||
|
||||
- Her pencere **ayrı process** (multi-instance). Her process'in kendi Skia/GL kaynakları **soğuk** başlar.
|
||||
- İlk kez `ColorFiltered` (color matrix shader) ve/veya `Transform` (rotate) kullanıldığında llvmpipe **shader'ı derler + ilgili render path'i kurar** — bu tek seferlik maliyet 1-10sn+ sürebilir (yazılımsal render, 4 CPU çekirdeği).
|
||||
- **Sonraki kullanımlarda shader cache'li** → donma tekrar etmez. "Bir kere oluyor, açıldıktan sonra tekrar olmuyor" = tam olarak bu.
|
||||
- **Tüm efektlerde olması**: brightness (03 Ağu'da eklendi, yeni birleşik 4x5 matrix) dahil her efekt kendi shader'ını ilk kez derliyor. Eskiden sadece rotate'te olması: o günlerde contrast/saturation zaten ısınmış/kullanılmıştı; rotate her oturumda en son eklenen ve en pahalı olandı.
|
||||
- **En çok rotate'te**: rotate iki shader path'ini birleştirir (ColorFiltered matrix + Transform composite) → en uzun ilk derleme.
|
||||
- **Çok pencerede olması**: her pencere soğuk başlar → her pencerede ilk efekt kullanımında ayrı donma. Tek pencerede donma tek seferlik olduğu için "sorun yok" gibi görünür.
|
||||
|
||||
### Aday B — llvmpipe raster thread uzun kare (arka plan maliyeti)
|
||||
|
||||
- Donma anında UI thread çalışıyor (işlemler işleniyor) ama görsel gecikiyor → raster/kompozisyon kuyruğu geride.
|
||||
- llvmpipe'ta her kare 15-50ms (03 Ağu ölçümü); ilk shader derlemesi bu kareleri 100x şişirir.
|
||||
- B1'in "ilk kullanım" tetikleyicisi, B'nin "uzun kare" mekanizması — ikisi aynı olayın iki yüzü: **soğuk kaynakla ilk efekt frame'i çok pahalı**.
|
||||
|
||||
### Aday C — Bellek baskısı / swap thrashing
|
||||
|
||||
- Çok pencerede olması C'yi güçlendirir (her process ayrı decode + doku). Ama "tekrar olmuyor" imzası C ile tam uymuyor (bellek baskısı her seferinde olur). İkincil faktör olabilir.
|
||||
|
||||
### Aday D — `_clamp()` build sırasında `findRenderObject()`
|
||||
|
||||
- Donma yapmaz (null döner), performans etkisi minimal. Kırmızı ringa.
|
||||
|
||||
## 4. Klavye Hipotezi Değerlendirmesi (kullanıcının şüphesi)
|
||||
|
||||
**Donmaya klavye neden olmuyor.** `_onHardwareKey` handler'ı sadece iki boolean set/clear ediyor — maliyeti ihmal edilebilir, render tetiklemiyor, event yutmuyor. Kullanıcının "farklı bir şey" sezgisi doğru.
|
||||
|
||||
Ancak klavye **yanlış mod tetikleme** (takılma hissi) üretebilir:
|
||||
|
||||
- X11'de Shift durumu pointer event sırasında güvenilmez (skill'de belgeli: `isShiftPressed` false dönebilir). `_onHardwareKey` ile takip doğru çözüm ama yine de key repeat / focus kaybı durumlarında state bozulabilir.
|
||||
- Shift basılıyken sağ tık → kullanıcı farkında olmadan **rotate** modu açılır → en pahalı efekt + beklenmedik davranış → "takılıyor" hissi.
|
||||
- Alt + tık XFCE WM tarafından yutulur (bilinen pitfall) — zaten Ctrl/Ctrl+Shift'e geçilmişti.
|
||||
|
||||
## 5. Slider UI Değerlendirmesi (kullanıcının önerisi)
|
||||
|
||||
**Kısa cevap: B1 (ilk kullanım shader derlemesi) teşhisi doğruysa, slider UI donmayı ÇÖZMEZ — çünkü donma render maliyetinden değil, ilk kullanımdaki tek seferlik shader derlemesinden kaynaklanıyor.** Slider ile contrast'ı ilk kez açtığın anda yine aynı shader derlenecek ve yine aynı donma olacak. Ama doğru tasarımla yine de değerli:
|
||||
|
||||
| Bileşen | Slider UI etkisi |
|
||||
|---|---|
|
||||
| İlk kullanım shader derlemesi (B1) | **Çözmez** — shader yine o an derlenir. Çözüm warmup (aşağıda) |
|
||||
| Olay hızı | **Aynı kalır** — slider da sürüklerken `onChanged` pointer hızında çağrılır. Ama ilk derleme bittikten sonra gerisi akıcıdır |
|
||||
| Mod bilinirliği | **Çözer** — hangi modda olduğun görünür; gizli modifier mapping'i biter. "Takılıyor" hissinin büyük kısmı modun ne olduğunu bilmemektir |
|
||||
| Klavye bağımlılığı | **Tamamen kaldırır** — `HardwareKeyboard` handler'ına, X11 Shift güvenilmezliğine, Alt yutma pitfall'ına gerek kalmaz |
|
||||
| Render maliyeti (ilk derleme sonrası) | **Ancak tasarımla düşer** — `onChangeEnd`'de uygula + sürükleme sırasında düşük çözünürlüklü önizleme (küçük cacheWidth) yapılırsa render başına maliyet ciddi düşer |
|
||||
|
||||
**Sonuç:** Slider UI tek başına "sihirli çözüm" değil. Donmanın asıl çözümü B1'e karşı: **shader warmup + ilk derlemeyi kullanıcı etkileşiminden önce yaptırmak**.
|
||||
|
||||
## 6. Önerilen Çözüm Yolları (B1 teşhisine göre)
|
||||
|
||||
### Seçenek 1 — Shader Warmup (ilk derlemeyi öne çek) ⭐ UYGULANDI (2026-08-06)
|
||||
|
||||
Açılışta, kullanıcı daha sağ tıka basmadan efekt shader'ları derletilir:
|
||||
|
||||
- `ImageCanvas.initState` → ilk frame sonrası `_startWarmup()`.
|
||||
- Görünmez 1x1 gizli katman (`_buildWarmupLayer`): adım 1'de `ColorFiltered` (filtre shader'ı), adım 2'de `ColorFiltered` + `Transform` rotate (rotate shader'ı) render edilir; sonra `_warmupDone = true` ile katman kaldırılır.
|
||||
- Kullanıcı ilk sağ tıka bastığında shader'lar hazırdır → donma hissi kaybolmalı.
|
||||
- Maliyet: pencere açılışında 2-3 ekstra gizli frame (arka planda, etkileşim yokken).
|
||||
- Not: çok pencerede her process kendi warmup'ını yapar — aynı mantık.
|
||||
- Doğrulama: release build + kurulum yapıldı (2026-08-06). Kullanıcı test edecek — ilk sağ tık donması yoksa teşhis doğrulanır.
|
||||
|
||||
### Seçenek 2 — Release build ile test (JIT yükü teyidi)
|
||||
|
||||
- **GÜNCELLEME:** Kullanıcı zaten release build kullanıyor (.deb'den kurulu) — JIT hipotezi **elendi**. Donma release AOT'ta da oluyor.
|
||||
- 15sn-1dk süre, llvmpipe'ta ilk render path kurulumunun (shader derleme + layer/doku hazırlığı) toplam maliyeti olabilir; "bir kez oluyor, tekrar olmuyor" imzası korunuyor.
|
||||
|
||||
### Seçenek 3 — Rotate maliyetini düşür
|
||||
|
||||
- Rotate aktifken `FilterQuality.high` → `FilterQuality.medium` (rotate sırasında görsel kalite farkı minimal, maliyet düşer).
|
||||
- `Transform` + `ColorFiltered` aynı anda aktifken tek composite path önerilir (mevcut yapı zaten tek ColorFiltered kullanıyor).
|
||||
|
||||
### Seçenek 4 — Slider UI (UX netliği, donma çözümü değil)
|
||||
|
||||
- Donma B1'den kaynaklanıyorsa slider UI donmayı çözmez; ama mod bilinirliği + klavye bağımsızlığı sağlar.
|
||||
- Warmup (Seçenek 1) uygulandıktan sonra slider UI istenirse eklenir — ikisi birlikte: donma yok + kullanım net.
|
||||
|
||||
## 7. Kalan Açık Sorular
|
||||
|
||||
1. Donma süresi yaklaşık ne kadar — 1-2sn mi, 10sn+ mi? (B1 shader derleme süresi tahminini doğrular)
|
||||
2. Debug build mi kullanıyorsun (install.sh ile kurulan), yoksa release mi? → Seçenek 2'yi doğrular
|
||||
3. Slider UI hâlâ isteniyor mu, yoksa önce donma çözümü mü? (Sıra: warmup → slider opsiyonel)
|
||||
4. Warmup için "açılışta arkada 1-3sn hazırlık" kabul edilir mi? (Alternatif: ilk efekt kullanımında 1 kez donma kabul edilir mi?)
|
||||
@@ -42,6 +42,10 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
final ValueNotifier<int> _renderTick = ValueNotifier(0);
|
||||
bool _frameScheduled = false;
|
||||
|
||||
// ── Shader warmup (ilk kullanım derlemesini açılışa çek) ──
|
||||
bool _warmupDone = false;
|
||||
int _warmupStep = 0; // 1=filtre, 2=filtre+rotate, 3=bitti
|
||||
|
||||
void _markRenderDirty() {
|
||||
if (_frameScheduled) return;
|
||||
_frameScheduled = true;
|
||||
@@ -116,6 +120,30 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
HardwareKeyboard.instance.addHandler(_onHardwareKey);
|
||||
_startWarmup();
|
||||
}
|
||||
|
||||
/// İlk kullanım shader derlemesini açılışa çek: gizli katmanda sırayla
|
||||
/// filtre ve rotate render edilir, sonra değerler sıfırlanır.
|
||||
void _startWarmup() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_warmupStep = 1;
|
||||
_markRenderDirty();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _warmupNext());
|
||||
});
|
||||
}
|
||||
|
||||
void _warmupNext() {
|
||||
if (!mounted) return;
|
||||
if (_warmupStep >= 2) {
|
||||
_warmupDone = true;
|
||||
_markRenderDirty();
|
||||
return;
|
||||
}
|
||||
_warmupStep++;
|
||||
_markRenderDirty();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _warmupNext());
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -432,11 +460,39 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildWarmupLayer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Görünmez 1x1 katman — ColorFiltered ve Transform shader'larını açılışta
|
||||
/// derletir; kullanıcı ilk sağ tıkta derleme beklemesin.
|
||||
Widget _buildWarmupLayer() {
|
||||
if (_warmupDone) return const SizedBox.shrink();
|
||||
final filter = ColorFilter.matrix(const <double>[
|
||||
1.5, 0, 0, 0, 0,
|
||||
0, 1.5, 0, 0, 0,
|
||||
0, 0, 1.5, 0, 0,
|
||||
0, 0, 0, 1, 0,
|
||||
]);
|
||||
Widget child = const SizedBox.expand();
|
||||
if (_warmupStep >= 2) {
|
||||
child = Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()..rotateZ(0.3),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
child = ColorFiltered(colorFilter: filter, child: child);
|
||||
return Opacity(
|
||||
opacity: 0,
|
||||
child: IgnorePointer(
|
||||
child: SizedBox(width: 1, height: 1, child: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _errorWidget() {
|
||||
return const Center(
|
||||
child: Column(
|
||||
|
||||
Reference in New Issue
Block a user