holodepth

Three.js · İleri geometri

Attributes Lifecycle & Buffer Management: Verinin GPU’daki Yaşam Döngüsü

Göndermek yetmez — bellekte ne kalacağını ve ne zaman güncelleneceğini yönetmek

Geometri optimizasyonu ve yapısal düzenler (interleaved, indexed) üzerinden geçtikten sonra, şimdi verinin GPU belleğindeki yönetim stratejilerine iniyoruz. Veriyi yalnızca oluşturmak yetmez; VRAM’da ne kadar süre tutulacağını, ne zaman serbest bırakılacağını ve güncellemenin nasıl sinyalleneceğini bilmek, HoloDepth tarzı yoğun sahnelerde CPU–GPU darboğazını yumuşatmanın merkezinde yer alır.

Ön okuma: Geometry optimization, Interleaved buffers, Attributes & buffer’lar, Instancing.

Bellek tahsisi: allocation ve deallocation

JavaScript tarafında çöp toplayıcı (garbage collector) nesne ömrünü yönetirken rahatlatır; oysa VRAM üzerinde otomatik bir “GC” yoktur. Bu da GPU kaynaklarının en sık sızıntı (leak) riski taşıdığı katmandır.

  • Tahsis: Bir BufferAttribute oluşturup geometriye bağladığınızda veri tipik olarak GPU’ya taşınır (ilk kullanım / bağlama anına göre motor davranışı değişebilir; fakat sorumluluk sizdedir).
  • İmha: scene.remove(mesh) yalnızca sahne grafiğinden çıkarır; VRAM’daki buffer’ı serbest bırakmaz. Kalıcı çözüm için geometry.dispose() (ve gerekiyorsa material.dispose(), dokular için texture.dispose()) çağrılmalıdır.

HoloDepth stratejisi: Dinamik sahnelerde sürekli yeni geometri üretmek yerine bir geometry pool ile nesneleri yeniden kullanmak, bellek parçalanmasını ve tarayıcıda gözle görülür takılmaları (stutter) azaltır.

Güncelleme kalıpları: needsUpdate bayrağı

Three.js performans için veriyi GPU’ya gönderdikten sonra “sabit” kabul eder. Typed array içeriğini CPU’da değiştirdiyseniz, değişikliğin bir sonraki çizimde yansıması için açık bir sinyal vermeniz gerekir:

// Örnek: pozisyon buffer'ı değiştirdikten sonra
const pos = geometry.attributes.position;
pos.needsUpdate = true;

Bu bayrak genellikle tüm attribute buffer’ının yeniden yüklenmesini tetikler. Milyonlarca vertex’iniz var ve yalnızca küçük bir aralık değiştiyse, Three.js r170+ sürümlerinde addUpdateRange(start, count) ile typed array içindeki eleman indeksleri (burada float ofseti) üzerinden kısmi bufferSubData yolu açılır; çizimden sonra motor aralıkları clearUpdateRanges() ile temizler. Eski sürümlerdeki tek nesne updateRange API’si bu sürümde yoktur.

// r170+: kısmi yükleme (start/count = typed array eleman indeksi)
const pos = geometry.attributes.position;
pos.clearUpdateRanges();
pos.addUpdateRange(startIndex * 3, vertexCount * 3); // örnek: vektör başına 3 float
pos.needsUpdate = true;

Gerçek projede güncelleme sıklığı, çakışan okuma/yazma (GPU hâlâ eski buffer’ı kullanırken CPU’nun yazması) ve sürücü politikaları birlikte düşünülmelidir; hemen altındaki buffer lifecycle laboratuvarı (demo sabitleri) ile tam / kısmi / orphan-style güncellemeyi canlı düzlemde kıyaslayın; ardından orphaning bölümünde bu gerilimi teorik olarak pekiştirin.

Buffer lifecycle laboratuvarı

Aşağıda tek bir düzlem üzerinde üç güncelleme stratejisini bir arada deneyebilirsiniz: tam buffer yazımı, addUpdateRange ile kısmi yükleme ve periyodik attribute replace (orphan fikrine yakın pratik). Üstteki şema veri akışını soyutlar; canlı sahne ise aynı geometri üzerinde davranışı somutlaştırır. Ölçüler demo sabitleri tablosunda doc-buffer-lifecycle-lab.js ile eşlenir.

Canlı · tek PlaneGeometry
Mod Aralık Sayaç

Upload (position · kabaca / kare)

  • TAM
  • KISMI
  • ORPHAN

Metin + şema + canlı sahne birlikte okunmalı: üstteki Upload satırları geometri boyutundan yerel hesaplanır (dış veri yok). Kısmi modda sarı çerçeve addUpdateRange ile eşleşen şeridi gösterir. Orphan-style’da periyodik yeni buffer + tam yükleme birlikte okunmalı.

Bu laboratuvar, sayfadaki needsUpdate, addUpdateRange ve buffer yeniden tahsis fikrini tek geometri üzerinde birleştirir; üretimde orphan genelde daha ince API ve çift buffer stratejileriyle kurulur — buradaki amaç sezgisel karşılaştırma sunmaktır.

Demo sabitleri tablosu (PlaneGeometry · buffer lifecycle)

initBufferLifecycleLab tek düzlemde üç modu ayırır; upload satırları fullBytes / partialBytes ile yerel hesaplanır.

initBufferLifecycleLab · lifecycle laboratuvarı
Sahne / rol Parametre Değer Tür
Izgara SEG / BAND 48 · 6 (satır sayısı SEG + 1) 🔒 Sabit
Düzlem PlaneGeometry 7.2×7.2 · segment SEG×SEG · rotateX(-π/2) 🔒 Sabit
Pozisyon buffer position.setUsage DynamicDrawUsage · base = kopya Float32Array 🔒 Sabit
Upload (tam) fullBytes base.byteLength · kabaca tam bufferSubData 🔒 Sabit
Upload (kısmi) partialBytes BAND × cols × 3 × 4 bayt · addUpdateRange şeridi 🔒 Sabit
Dalga (tam / orphan CPU) applyWaveAll y = baseY + sin(x×0.62 + t×0.0021) × 0.26 × cos(z×0.48 + t×0.0016) · t kare başı performance.now() × 0.001 🔒 Sabit
Dalga (kısmi şerit) applyWaveRows aynı faz · genlik çarpanı 0.32 (şerit içi) 🔒 Sabit
Kısmi aralık row0 / addUpdateRange row0 = floor((sin(t×0.55)×0.5+0.5) × max(0, rows - BAND)) · offset row0 × floatsPerRow · count BAND × floatsPerRow 🔒 Sabit
Orphan-style tetik frame frame > 20 ve frame % 78 === 0 → yeni BufferAttribute 🔒 Koşul
Gövde materyal MeshBasicMaterial 0x5ec8ff · DoubleSide 🔒 Sabit
Şerit çerçeve LineLoop 0xffd24a · opaklık 0.95 · kısmi modda görünür 🔒 Koşul
Işık AmbientLight beyaz · yoğunluk 0.92 🔒 Sabit
Kamera PerspectiveCamera FOV 42 · yakın/uzak 0.08 / 120 · konum (3.8, 2.85, 4.6) 🔒 Sabit
OrbitControls target / dampingFactor (0, 0.05, 0) · 0.07 · maxPolarAngle 0.48π 🔒 Sabit
Renderer makeRenderer ACESFilmicToneMapping · exposure 1.22 · SRGBColorSpace · setPixelRatio(min(dpr,2)) · arka plan 0x04060e 🔒 Sabit
CPU göstergesi cpuEma üç mod için EMA · EMA_A = 0.14 🔒 Sabit
JS başlangıç mode 'full' (HTML: Tam güncelleme seçili) 🔒 + ↔ UI

Önemli kod kesiti

Orphan-style eğitim örneğinde pozisyon attribute’u periyodik olarak yeniden tahsis edilir; yeni Float32Array + BufferAttribute ile değiştirilir.

function doOrphanReplace(t) {
  const arr = new Float32Array(base.length);
  arr.set(base);
  applyWaveAll(arr, t);
  const na = new THREE.BufferAttribute(arr, 3);
  na.setUsage(THREE.DynamicDrawUsage);
  geo.deleteAttribute('position');
  geo.setAttribute('position', na);
  posAttr = geo.attributes.position;
  orphanTicks++;
  orphanReplacedThisFrame = true;
}

Buffer usage ipucu: GPU’ya yol göstermek

Kullanım ipucu (setUsage / BufferAttribute oluştururken seçilen mod), sürücünün veriyi hangi bellek davranışıyla ele alacağına dair önceden bilgi verir. Three.js tarafında yaygın karşılıklar:

Three.js WebGL karşılığı (kavram) Tipik kullanım
THREE.StaticDrawUsage STATIC_DRAW Veri bir kez yazılır, çok kez çizilir (statik çevre, prop).
THREE.DynamicDrawUsage DYNAMIC_DRAW Veri sık sık güncellenir (simülasyon, dalga, deformasyon).
THREE.StreamDrawUsage STREAM_DRAW Veri genelde her karede değişir ve sınırlı sayıda çizimde kullanılır (geçici efekt, tek seferlik burst).

Yanlış ipucu, gereksiz senkronizasyon veya tam tersi “her şeyi dynamic sanma” hatalarına yol açabilir; pratikte ölçüm (profiler, renderer.info) ile doğrulanır.

Buffer orphaning ve akıcılık

GPU bir buffer üzerinden çizim yaparken CPU’nun aynı buffer’a aniden ağır yazım yapması, sürücüde senkronizasyon noktası (stall) riskini artırır.

İleri seviye bir yaklaşım olan orphaning (yetim bırakma), mevcut buffer’ı GPU işini bitirene kadar “eski” sayıp aynı boyutta yeni bir alan tahsis etmeyi içerir: CPU yeni alana yazar, GPU eski alandan okumaya devam eder; geçiş tamamlandığında eski blok temizlenir. Yüksek yenileme hızlarında (ör. 120 Hz) kare düşürmesini azaltmak için kullanılan stratejilerden biridir — uygulama detayı API ve sürücüye göre değişir; WebGL katmanında genelde bufferData ile “yeniden boyutlandırma / replace” kalıpları bu fikre yaklaşır.

Interleaving ile yönetim (HoloDepth ipucu)

Birden fazla attribute’u (ör. position, color, uv) ayrı ayrı güncellemek yerine, tek bir interleaved buffer üzerinden vertex bloğunu güncellemek CPU tarafında daha az dağıtık iş ve daha iyi önbellek yerelliği sağlar; WebGL’de tipik olarak tek bir bufferSubData (veya eşdeğeri) ile daha geniş blok güncellenebilir.

Ayrıntılı düzen ve stride mantığı için Interleaved buffers sayfasına dönün; yaşam döngüsü perspektifinde “tek kaynak, tek güncelleme ritmi” hedefini koruyun.

Özet: verinin hakimi olun

Advanced Geometries hattında bu bölümde verinin yalnızca ne olduğunu değil, bellekte nasıl yaşadığını özetledik: Geometry optimization ile yükü hafifletmiştik; buffer yaşam döngüsü ve güncelleme disiplini ile bu yükün taşınma hızını ve maliyetini kontrol altına alırsınız.