holodepth

HTML5 Canvas · 2D–WebGL köprüsü

CPU işlemesi ve GPU işlemesi: Canvas geliştiricisi için rol haritası

Tarayıcı oyununda «iş» hem ana iş parçacığında ( CPU üzerinde çalışan JavaScript ve düzen motoru ) hem grafik işlemcisinde ( GPU üzerinde yürütülen raster ve sayfadırıcı işleri ) paylaşılır. Canvas 2D ile başlayan geliştirici için kritik fark: komutları yazdığınız yer ile piksellerin gerçekten boyandığı yer aynı kod satırında görünmez — köprüye geçerken bu ayrım netleşmelidir. Bu sayfa genel donanım dersi vermez; odak Canvas 2D bağlamı ile WebGL bağlamının masraflarını karşılaştırmak ve doğru API seçimini gerekçelendirmektir.

Piksel başına maliyet ve bağlam sıçraması Yeniden çizim maliyeti ile işlenir; çizim çağrılarının sırası Batching mantığı başlığında kalır — burada tekrar edilmez. Geniş özet için köprü okuması: GPU vs CPU. Komşu köprü konuları: İş hattı farkları, Canvas sınırları, Neden WebGL?

Özet: iki bağlamda iş kimde?

Konu CPU tarafı GPU tarafı
Canvas 2D çizimi Komut üretimi, stil ve sıra Raster / bileşim ( uygulamaya bağlı)
WebGL çizimi Tampon yükleme, durum, çağrı Vertex / fragment programları
Geri okuma ( readback ) Sonuç JS belleğine aktarılır Senkron beklemeye yol açabilir

Rol ayrımı ve tarayıcı içindeki iki dünya

CPU üzerinde çalışan kodunuz çizim kararını üretir: hangi sprite nerede, hangi renk, hangi yüzey güncellenmeli. Bu kararlar Canvas 2D’de çoğu zaman tek tek çağrıya dökülür; WebGL’de ise tamponlara yazılmış geometri ve örnekleyici tarafından tetiklenir. GPU ise aynı anda çok sayıda piksel veya köşe üzerinde paralel iş yükünü yürütmeye uygundur — fakat bu paralellik «her iş GPU’da bedavadır» anlamına gelmez; veriyi GPU’ya taşımak, durumu kurmak ve senkron beklemek yine CPU zamanınızdan yer.

Canvas 2D uygulaması yazarken zihinsel model çoğu zaman «tuvala komut yazıyorum» şeklindedir; WebGL’ye geçişte model «durum makinesi + gönder ( dispatch )» haline gelir. Köprü öğrenmesi: aynı sahneyi düşünün — birinde çağrı sırası okunabilirlik sunar, diğerinde veri hazırlığı ve gölgelendirici ( shader ) kontratı öne çıkar. Hangi yaklaşımın daha az sürdürülebilir olduğu projeye bağlıdır; bu sayfa «WebGL her zaman kazanır» demez.

Tarayıcı ve işletim sistemi sürücüleri arasında ek katmanlar vardır; uygulama kodunuz doğrudan donanımı yönetmez. Bu yüzden mikro düzeyde «şu komut GPU’da şu kadar döngü» iddiası yerine ölçüm ve köprü kavramları ile ilerlemek daha güvenilirdir.

Canvas 2D ve hemen çizim düşüncesinin maliyeti

Canvas 2D API’si geliştiriciye «şimdi şunu çiz» hissi verir; motor ise bu çağrıları içte işleyip birleştirilmiş çıktı üretir. Maliyet iki eksende büyür: işlediğiniz piksel sayısı ve bağlam durumunun ne sıklıkla değiştiği ( dolgu, süzgeç, birleştirme modu ). Bu ikinci eksen bağlam maliyeti başlığında sabitlenmiştir — CPU tarafında sık özellik değişimi genelde daha görünür bir profil imzası üretir.

Canvas 2D birçok kullanım için yeterince hızlıdır: kullanıcı arayüzleri, basit 2D oyunlar, grafik çizimleri. Köprü sorusu şudur: «aynı sahneyi daha fazla öznitelik ve shader kontrolü ile sürdürebilir miyim?» — hayır ise WebGL düşünmek için tek başına CPU/GPU ayrımı yetmez; sahne karmaşıklığı ve ürün yol haritası gerekir.

Bellekte ikinci bir tampon ( Offscreen Canvas ) kullanmak CPU tarafında hazırlığı artırır fakat ana döngüde raster süresini doğru tasarlandığında düşürebilir — bu, GPU yerine «işin nerede yapıldığını seçme» örneğidir.

WebGL gönderim modeli ve CPU’nun hazırlık rolü

WebGL’de çizim tipik olarak şöyle akar: tamponlar ( konum, doku koordinatı vb. ) GPU belleğine veya paylaşılan belleğe yüklenir, gölgelendirici programı seçilir, birleştirme ve derinlik durumu ayarlanır, ardından drawArrays / drawElements ( veya örneklenmiş varyantlar ) ile gönderim yapılır. Bu gönderimin her biri CPU tarafından tetiklenir; yani «GPU çiziyor» doğru olsa da «CPU hiç çalışmıyor» yanlıştır.

Köprü için kritik içgörü: WebGL performansı çoğu zaman çağrı sayısı ve durum değişimi ile sınırlanır — Canvas 2D’deki bağlam sıçraması ile aynı sınıf düşünce, farklı API yüzeyi. Bu yüzden WebGL tarafında da toplu geometri ( batching ), örneklenmiş çizim ( instancing ) ve dokuların yeniden kullanımı önemlidir; detay Three.js veya ham WebGL kurslarında genişler — burada yalnızca CPU hazırlığı ↔ GPU yürütmesi dengesi işaretlenir.

Aşağıdaki yardımcı, sayfada halihazırda var olan bir tuval öğesi üzerinde WebGL bağlamı açılıp açılamayacağını güvenli şekilde dener; oluşturucuya kullanıcı girdisi bağlanmaz. Üretimde bağlam kaybı ve güç tercihi ( powerPreference ) politikanıza göre ayarlanır.

/**
 * WebGL bağlamı güvenli dene — şirket içi politikaya göre seçenekleri daraltın.
 * @param {HTMLCanvasElement} canvas
 * @returns {WebGLRenderingContext | WebGL2RenderingContext | null}
 */
export function tryWebGLContext(canvas) {
  if (!(canvas instanceof HTMLCanvasElement)) return null;

  const opts = {
    alpha: true,
    antialias: true,
    depth: true,
    stencil: false,
    powerPreference: 'default',
    premultipliedAlpha: true,
    preserveDrawingBuffer: false,
    failIfMajorPerformanceCaveat: false,
  };

  try {
    return (
      canvas.getContext('webgl2', opts) ||
      canvas.getContext('webgl', opts) ||
      canvas.getContext('experimental-webgl', opts)
    );
  } catch {
    return null;
  }
}

Paralellik: GPU’nun güçlü ve zayıf uyduğu işler

GPU, aynı gölgelendirici programını çok sayıda piksel veya köşe üzerinde paralel yürütmeye uygundur; bu nedenle tam ekran efektler, yoğun parçacık benzeri çıktılar ve tekrarlayan örüntüler için çekicidir. Ancak dallanmalı ( branchy ) mantık ( her pikselde farklı koşul ) paralelliği kırabilir; düzensiz bellek erişimi ( önbellek dostu olmayan örnekleme ) verim düşürür. Bu «uygun iş yükü» düşüncesi, Canvas 2D’den WebGL’ye geçiş kararında sahneyi tanımlamak için gereklidir.

CPU tarafında ise düzensiz mantık, sahne grafiği gezintisi ve giriş işleme doğaldır. Köprü kararı: yoğun paralel raster işini GPU programına taşıyıp CPU’yu düşük frekanslı simülasyon ve hazırlığa ayırmak — fakat taşımanın bedeli gölgelendirici yazımı, tampon yaşam döngüsü ve hata ayıklama maliyetidir.

Tek başına «GPU daha hızlıdır» ifadesi yanıltıcıdır; küçük geometri ve az çağrı ile WebGL, sabit durum maliyeti yüzünden beklenenden yavaş görünebilir. Ölçüm ve sahne büyüklüğü olmadan araç seçimi yapılmamalıdır.

Veri yolu: yükleme, dokular ve Canvas ile köprü

GPU’ya veri göndermek bant genişliği ve düzen ( hizalama, sıkıştırma ) ile ilgilidir. Decode edilmiş görüntüler WebGL dokusu olarak yüklenir; Canvas 2D’de ise drawImage ile doğrudan bitmap kaynak kullanılabilir. Köprü senaryosu: aynı decode edilmiş görsel hem 2D katmanda hem WebGL dokusu olarak yaşayabilir — çift tutmanın bellek maliyeti vardır; tek kaynakta birleştirmek için ortak yükleme politikası gerekir. Görsel yükleme akışı burada tekrar edilmez.

Her karede büyük tamponları yeniden yüklemek ( streaming hariç bilinçli tasarım ) maliyetlidir — CPU hazırlığı ve veri yolu trafiği artar. Bu yüzden WebGL tarafında «dinamik güncellenen dokular» ile Canvas 2D’de «her kare tam boy güncelleme» benzer şekilde şişebilir; köprü mimarisinde hangi verinin ne sıklıkta taşındığını belgelemek önemlidir.

Aşağıdaki örnek, küçük bir yardımcı tuval üzerinde bir kerelik bağlam sondası yapar; ana görünür tuvalinize dokunmaz. Sonuç önbelleğe alınır — çağrıyı uygulama başında bir kez yapın; her kare çağırmak bağlam oluşturma maliyeti doğurur ( üretimde tek seferlik özellik algılama kalıbı ).

/** @type {{ webgl2: boolean, webgl1: boolean, d2: boolean } | null} */
let cachedProbe = null;

/**
 * Uygulama başında bir kez çağrın; dahili 1×1 tuvalde bağlam dener (
 * görünür tuvalinize dokunmaz ).
 */
export function probeGraphicsBackendsOnce() {
  if (cachedProbe) return cachedProbe;

  const out = { webgl2: false, webgl1: false, d2: false };
  try {
    const probe = () => {
      const c = document.createElement('canvas');
      c.width = 1;
      c.height = 1;
      return c;
    };
    out.webgl2 = !!probe().getContext('webgl2');
    const c1 = probe();
    out.webgl1 = !!(c1.getContext('webgl') || c1.getContext('experimental-webgl'));
    out.d2 = !!probe().getContext('2d');
  } catch {
    /* algılama başarısız — güvenli varsayılanlar */
  }

  cachedProbe = out;
  return out;
}

Okuma geri senkronu ve ana iş parçacığında takılma

GPU’dan CPU’ya veri geri okumak ( readback ) genelde pahalıdır: boru hattı tamamlanmayı bekleyebilir, ana iş parçacığında ani süre sıçramaları görülebilir. Canvas 2D’de getImageData ve WebGL’de readPixels bu sınıftır — her karede tam ekran okuma nadiren sürdürülebilir. Köprü tasarımında «GPU’da hesaplayıp CPU’da her kare tüketmek» yerine sonucu mümkün olduğunca GPU üzerinde tutmak veya okumayı seyrekleştirmek tipik rahatlamadır.

Bu kısıt, Canvas ile WebGL birlikte kullanılan hibrit düzenlerde özellikle önemlidir: örneğin WebGL çıktısını her karede piksel piksel işlemek için CPU’ya aktarmak köprünün amacını boşa çıkarabilir. İş birimi olarak dokular ve tamponlar üzerinden düşünmek ve okuma noktalarını belgelemek gerekir.

Kare bütçesi ölçümü için deterministik bir zaman çerçevesi iskeleti ( aşağıda ) kullanılabilir; gerçek zamanlayıcı politikası ( performance.now, rAF ) projenize bağlıdır — örnek doğrudan kullanıcı girdisi içermez.

/**
 * Kare içi işleri üst süre ile sınırlamak için basit sayaç (
 * ölçüm / erken çıkış kalıbı — gerçek zaman kaynağı çağıran ekler).
 * @param {number} maxMs - Pozitif milisaniye üst sınırı
 */
export function createFrameBudget(maxMs) {
  const cap = Math.max(0.5, Number(maxMs) || 16);
  let start = 0;

  return {
    begin(nowMs) {
      start = Number(nowMs) || 0;
    },
    /** Kalan süre; 0 veya negatif ise süre dolmuş demektir. */
    remaining(nowMs) {
      const now = Number(nowMs) || start;
      return cap - (now - start);
    },
    capMs() {
      return cap;
    },
  };
}

Araç seçimi özeti, tuzaklar ve kontrol listesi

Canvas 2D: düşük sürtümeli prototip, doğrudan çizim, 2D odaklı ürünler için güçlüdür. WebGL: özel gölgelendirici, derinlik / stencil, çok öğeli 3D veya yoğun paralel raster gereksinimi için güçlüdür. Köprü kararı salt «CPU mu GPU mu» değil; sürdürülebilirlik ve ekip yetkinliği de tartıya girer.

Yaygın tuzaklar: WebGL’yi az geometri ile kullanıp sabit maliyet ödemek; her kare GPU→CPU okuma yapmak; Canvas 2D’de pahalı süzgeci her sprite için açık unutmak ( bağlantılı başlık üzerinden özetlenmiştir ); köprü kodunda çift bağlamı koordinasyonsuz bırakmak. Bu liste Canvas sınırları ve Neden WebGL? ile birlikte tamamlanır.

probeGraphicsBackendsOnce dahili yardımcı tuvalde bağlam dener; ana tuvalinizi kirletmez — yine de sıklığı düşük tutun ( önbellek tek seferlik kullanım içindir ).

Bu sayfanın sınırı

Vulkan, Metal veya doğrudan sürücü düzeyi optimizasyonları işlenmez. WebGPU ayrı başlık olarak haritada yer alır; bu sayfa Canvas geliştiricisinin WebGL köprüsüne hazırlığı için CPU/GPU rol ayrımını sabitler.

  • Profilde darboğaz CPU hazırlığı mı, GPU süresi mi, okuma geri senkronu mu?
  • Canvas 2D bağlam sıçraması ve piksel bütçesi ölçüldü mü?
  • WebGL tarafında gönderim ve durum değişimi sınırlı mı?
  • Her kare okuma ( readback ) gerekliliği sorgulandı mı?
  • Köprü mimarisinde tek kaynaklı doku / tampon politikası var mı?