Render Loop
Render loop, 3D uygulamanın kalp atışıdır: her karede önce dünya güncellenir, sonra
ekrana çizilir. Bu ritim doğru kurulmazsa; stutter, tearing, gereksiz CPU yükü ve
tutarsız hareketler kaçınılmaz olur.
Bu sayfada loop sezgisini, update→render ayrımını, rAF zamanlamasını ve delta time ile
gerçek-zaman tutarlılığını kuracağız.
Loop Nedir? Film Şeridi Sezgisi
3D sahneler birer optik illüzyondur: durağan kareleri çok hızlı ardı ardına gösterdiğinde beynin bunu "hareket" olarak algılar. 60 FPS hedefi, saniyede yaklaşık 60 kez "yeni bir kare" üretebilmek demektir.
Bu yüzden 3D uygulama "yüklenip bekleyen" bir sayfa değildir; kendi kendini tekrar eden canlı bir sistemdir. Kullanıcı hiçbir şey yapmasa bile sahnenin zamanı akar ve loop ritmi çalışır.
Bu ritmin "kuralı" kare bütçesidir: 60 FPS hedefinde bir kare için yaklaşık \(\frac{1000}{60} \approx 16.67ms\) zamanın vardır. Bu pencere içinde hem mantık (update) hem çizim (render) hem de tarayıcının kendi işleri (layout, input, GC vb.) aynı bütçeyi paylaşır.
Bu yüzden 3D’de performans, sadece "hızlı hesap" değil; ritmi bozmama disiplinidir. Tek bir karede uzun süren bir iş (long task) oluşursa, göz bunu "takılma" (stutter) olarak hisseder; çünkü film şeridinde bir kare gecikmiştir.
Kısa karşılaştırma: Statik sayfa çoğu zaman olay bekler; render loop ise her karede "ne değişti?" sorusunu tekrar sorar. Bu farkı doğru kurmak, 3D web’de performans ve mimari için temel adımdır.
Pratik sezgi: Eğer "hareket var ama kopuk" hissediyorsan, sorun çoğu zaman ortalama FPS değil; karelerin tutarsız gelmesidir. Yani 60 FPS yazsa bile bazı kareler 8ms, bazı kareler 40ms sürüyorsa kullanıcı bunu akıcı görmez.
Bu yaklaşımı GPU vs CPU sayfasındaki iş bölümüyle birlikte okuduğunda tablo netleşir: CPU kare başına karar verir, GPU tekrar eden işi yürütür; ama ritmi bozan şey genelde CPU’daki beklenmedik uzun işler veya köprü trafiğidir.
İdeal Akış Update → Render
Her döngü adımında (tick) iki ana iş vardır. Update aşamasında "bu karede ne değişti?" sorusunu cevaplarsın: konumlar, animasyon zamanları, çarpışmalar, input ve oyun mantığı genelde burada akar. Bu katman çoğunlukla CPU tarafındadır.
Render aşamasında ise güncel state’e göre sahneyi ekrana basarsın. Bu aşama, komutların GPU’ya gönderilmesi ve pipeline’ın yürütülmesi anlamına gelir.
Bu ayrım, "hesap nerede olmalı?" sorusunu da netleştirir: update tarafında karar ve kontrol vardır; render tarafında tekrar ve paralel yürütme vardır. Bu yüzden render aşamasına "gereksiz iş" eklemek, GPU’yu değil çoğu zaman CPU’yu ve köprüyü yorar (fazla state değişimi, fazla draw call, gereksiz veri upload).
İyi bir loop tasarımında hedef, update’i tahmin edilebilir ve kısa tutmaktır: aynı kare bütçesinde 5ms → 25ms zıplamak, kullanıcıya "takılma" olarak döner. Bu yüzden büyük işler ya parçalara bölünür ya da kritik yolun dışına alınır.
Pratik sezgi: Update "dünyayı düzeltir", render "dünyayı gösterir". Bu ikisini karıştırmak (ör. render içinde ağır hesap yapmak) kare bütçesini hızla tüketir.
Bir adım daha: Update’in içinde bile "simülasyon" ile "state yönetimi"ni ayırmak faydalıdır. Örneğin input okuma, fizik/çarpışma, animasyon zamanları ve sahne grafiği güncellemesi aynı karede olsa bile, ayrı bloklar halinde tutulduğunda hem debug hem optimizasyon kolaylaşır.
Zamanlama Neden setInterval Yetmez?
Web’de döngü kurmak denince akla gelen araçlardan biri setInterval’dır; fakat 3D dünyasında tehlikelidir. Çünkü ekranın tazeleme hızından bağımsız çalışır: ekran hazır değilken çizim zorlamak "tearing" ve gereksiz CPU/GPU baskısı üretir.
Bir diğer problem "drift"tir: setInterval teoride "her 16ms" dese de, pratikte tarayıcı yükü, ana iş parçacığı yoğunluğu ve zamanlayıcı çözünürlüğü nedeniyle bu aralıklar kayar. Sonuç olarak kareler düzensiz gelir ve kullanıcı bunu akıcılık kaybı olarak hisseder.
Gizli maliyet: setInterval, sekme arka plandayken bile bir süre çalışmaya devam edebilir (tarayıcıya göre throttle edilse de). Bu durum gereksiz CPU uyanmaları ve batarya tüketimi demektir. 3D gibi "sürekli üretim" yapan sistemlerde bu maliyet daha görünür olur.
Çözüm: requestAnimationFrame (rAF)requestAnimationFrame, tarayıcıya "ekran yeni kare çizmeye hazır olduğunda beni çağır" demektir. Bu sayede loop, monitörün refresh rate’i ile senkronize olur (60Hz/144Hz) ve sekme arka plana düştüğünde otomatik yavaşlar/durur (batarya dostu).
rAF’in bir diğer avantajı, zaman bilgisini "doğru" vermesidir: callback’e gelen zaman damgası ile \(\Delta t\) hesaplayabilir ve hareketi buna göre ölçekleyebilirsin. Yani rAF, sadece "ne zaman çizileceğini" değil, aynı zamanda "ne kadar zaman geçtiğini" de loop’a taşır.
Pratik sezgi: setInterval "kendince ritim" kurar; rAF ise "ekran ritmine" bağlanır. 3D’de hedef, kendi ritmini dayatmak değil; VSync ile uyumlu bir üretim hattı kurmaktır.
İçerik Odağı: rAF, "doğru zamanda çiz" disiplinidir. 3D’de zamanlama hatası, çoğu performans probleminden önce gelir.
Bu yüzden 3D’de genelde şu düzeni görürsün: rAF içinde \(\Delta t\) hesapla → update’i çalıştır → render’ı çağır. Böylece hem "ne zaman çiziyorum?" hem "neye göre güncelliyorum?" soruları tek ritimde birleşir.
Delta Time FPS Bağımsızlığı
Döngünün en büyük düşmanı değişken FPS’dir. Hızlı makinada loop 120 kez, yavaş makinada 30 kez dönerse; aynı kod "farklı hızda" çalışır. İşte bu yüzden iki kare arasındaki süreyi (\(\Delta t\)) ölçer ve hareketi buna göre ölçeklersin.
Temel formül şudur: \(\text{konum} \; += \; \text{hız} \times \Delta t\). Böylece FPS kaç olursa olsun, obje gerçek saniyede aynı mesafeyi kateder.
Burada kritik olan, hızın birimidir: "saniye başına birim" (units/sec) gibi düşünürsen, \(\Delta t\) zaten "saniye" olduğu için çarpımın sonucu doğru ölçeğe oturur. Bu da hareketi makineden bağımsız yapar. Vektör tarafındaki bu sezgi için Toplama ve Çıkarma bölümündeki \(\text{pos} \leftarrow \text{pos} + \text{vel}\cdot dt\) fikriyle aynı çizgidedir.
Pratik not: \(\Delta t\) çok büyürse (sekme geri geldi, frame kaçırıldı) hareket "zıplayabilir". Bu yüzden bazı sistemler \(\Delta t\)’yi üstten sınırlar (clamp) veya fixed-step simülasyon uygular.
"Clamp" yaklaşımı, görsel stabiliteyi korur ama fizik benzeri simülasyonlarda bir tür "zaman kaybı" yaratabilir. Bu yüzden bazı projelerde fixed-step tercih edilir: zamanı bir akümülatörde toplar, simülasyonu sabit adımlarla (ör. 1/60s) birden çok kez çalıştırır, sonra render ile ekrana basarsın.
Mini sezgi: Delta time "hızı düzeltir", fixed-step "simülasyon kararlılığını" düzeltir. Hareket basitse \(\Delta t\) yeterlidir; çarpışma/fizik ağırsa fixed-step daha tutarlı sonuç verir.
HoloDepth Lab: Loop ve Ritim DeneyleriDöngünün hızını ve zamanın gücünü bizzat kontrol etmek ister misin? HoloDepth laboratuvarında sahnenin "kalp atışını" manipüle ediyoruz: FPS limiter ile stutter gözlemi, delta time testleri ve arka plan pause davranışı gibi.
Neleri deneyebilirsin? FPS’i 10’a düşürüp takılmayı gör; delta time ile ve delta time’sız iki objeyi kıyasla; sekme değiştirince rAF’in nasıl durduğunu izle.
Dünyanın Kalp Atışını Yönetin
Zamanın akışına müdahale edin. Delta Time ile düşük FPS’te bile hareketin nasıl korunduğunu görün; requestAnimationFrame ritminin tarayıcıyla senkronunu bizzat test edin.
FPS’i düşür, sekme değiştir, geri dön. rAF’in durma/akma davranışını gözle ve delta time etkisini aynı ekranda kıyasla.