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
BufferAttributeoluş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çingeometry.dispose()(ve gerekiyorsamaterial.dispose(), dokular içintexture.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.
Veri yükleme modları (şema)
Bu çizim üç ayrı stratejiyi alt alta gösterir; her satır “bir karede GPU’ya ne kadar veri gider?” fikrini basitçe özetler. Gerçek sayılar alttaki laboratuvarda yazılır; burada yalnızca kalıp var.
Üst satır (tam): Şeridin tamamı yeşil — neredeyse tüm köşe konumlarını her seferinde yeniden yüklersiniz; anlaması kolay, bant genişliği açısından en “dolu” seçenektir.
Orta satır (kısmi): Kenarlar koyu, ortası sarı — yalnızca o dar bölümdeki noktalar güncellenir; geri kalanı bu karede dokunulmazmış gibi düşünün.
Alt satır (orphan tarzı): Soldaki kutu “GPU’da duran eski blok”, sağdaki “CPU’da hazırlanan yeni blok”, ok ise “bir ara yeni bloğa geç” fikrini temsil eder; gerçek sürücüde işler daha ince yürür, ama sezgi bu üçlüden çıkar.
- Tüm vertex CPU’da yazılır, GPU’ya tam blok
-
Yalnız şerit güncellenir;
addUpdateRangedar -
Yeni
BufferAttributeile replace (eğitimsel basitleştirme)
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.
| 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.