holodepth

HTML5 Canvas · Performans ve optimizasyon

Offscreen Canvas: DOM dışı tampon ve işçi iş parçacığı rasterı

OffscreenCanvas, bellekte tutulan ve sayfada görünmeyen bir çizim yüzeyidir; görünür <canvas> öğesinden bağımsız olarak aynı 2D bağlam modelini sunar. Amaç: ağır veya seyrek güncellenen grafikleri ana iş parçacığından ayırmak, katmanları önceden üretmek ve gerektiğinde worker içinde pikselleri hazırlayıp sonucu güvenli biçimde görünür tuvala aktarmaktır. Bu sayfa Canvas 2D bakış açısıyla kalıpları sabitler; Katman tuval ve yeniden boyutlandırma düşüncesiyle birlikte okunmalıdır — tam ekran «her kare sil–çiz» maliyetinin özeti Yeniden çizim maliyeti başlığındadır.

WebGL tabanlı transferControlToOffscreen ve bağlam devri bu sayfanın odağı değildir; burada öncelik Bitmap / 2D raster çıktısının üretilmesi ve köprülenmesidir. Nesne tahsisatını azaltmak için Nesne havuzlama komşu başlıkta işlenir.

Özet: ne zaman işe yarar?

Desen Kazanç Ödeme
Ayrı tampon / katman önbake Ana karede daha az doğrudan çizim Ek bellek, boyut senkronu
Worker + ImageBitmap Uzun raster ana iş parçacığında bloklamaz Kopya / aktarım ve uyumluluk kontrolü
Düşük frekanslı etiket / atlas Metin ve karmaşık yollar tek sefer Güncellemede tampon yenileme disiplini

Offscreen Canvas nedir ve tamponun konumu

Tarayıcı belleğinde tutulan bir çizim yüzeyi düşünün: hem genişlik / yükseklik ayarı yapılır hem de getContext('2d') ile bağlam alınır; ancak belgede karşılığı olan bir kutu yoktur — işte bu yüzey OffscreenCanvas ile temsil edilir. DOM’a bağlı olmayan ikinci bir <canvas> oluşturmak ( document.createElement('canvas') sonrası DOM’a eklememek ) benzer bir tampon sağlar; standart API ise oluşturma ve işçi iş parçacığı aktarımını öngörür şekilde ortaktır.

Görünür tuval ile ilişki tipik olarak tek yönlüdür: önce görünmez yüzeyde çizersiniz, sonra drawImage ile görünür bağlamda yapıştırırsınız veya ImageBitmap üzerinden aktarırsınız. Bu desen, tek başına «otomatik hızlanma» değildir — her karede tam boy görüntü kopyalarsanız piksel sayısı yine maliyetinizi belirler ( piksel bütçesi ). Kazanım, işin nerede ve ne sıklıkta yapıldığını seçebilmenizdedir.

Çift tampon ( double buffering ) düşüncesine yakınsar: bir yüzey «arka planda» güncellenir, bir başka karede birleştirilir; fakat tarayıcı zaten kendi bileşim yolunu kullanır — burada odak, uygulama düzeyinde hangi katmanların seyrek güncellendiğini ayırmaktır. Katman stratejisi ile doğrudan örtüşür; bu sayfa API düzeyini tamamlar.

Güvenli oluşturma ve ortam denetimi

Üretim kodunda özellik varlığını kontrol etmek zorunludur: eski tarayıcılar veya kısıtlı ortamlarda OffscreenCanvas oluşturucusu bulunmayabilir. Güvenli yaklaşım: önce API’yi dene, başarısızsa DOM’suz klasik <canvas> örneği oluştur — ikisi de aynı 2D çizim komutlarını kabul eder; tek fark taşıma ve işçi iş parçacığı ergonomisiidir.

Aşağıdaki yardımcı hiçbir dış kaynağa bağlanmaz; kullanıcı girdisi kullanmaz ve yalnız boyut doğrulaması yapar. Gerçek projede üst sınır ( maksimum genişlik / yükseklik ) ürün politikasıyla sabitlenmeli, aksi halde tek bir hatalı ölçüm bellekte çok büyük tampon tetikleyebilir — bu bir güvenlik / kararlılık konusudur, sadece performans değil.

/**
 * Görünmez tampon: OffscreenCanvas veya DOM'suz canvas.
 * @param {number} widthDevicePx - Cihaz pikselleri (tam sayı, > 0).
 * @param {number} heightDevicePx
 * @returns {{ canvas: OffscreenCanvas|HTMLCanvasElement, kind: 'offscreen'|'canvas' }}
 */
export function createDrawingBuffer(widthDevicePx, heightDevicePx) {
  const w = Math.max(1, Math.floor(Number(widthDevicePx)));
  const h = Math.max(1, Math.floor(Number(heightDevicePx)));

  if (typeof OffscreenCanvas !== 'undefined') {
    return { canvas: new OffscreenCanvas(w, h), kind: 'offscreen' };
  }

  const fallback = document.createElement('canvas');
  fallback.width = w;
  fallback.height = h;
  return { canvas: fallback, kind: 'canvas' };
}

İki boyutlu bağlam ve görünür tuval köprüsü

Hem OffscreenCanvas hem de görünür öğe üzerindeki bağlam aynı çizim komutlarını anlar; fark, kullanıcı arayüzünde doğrudan görünmemenizdir. Köprüleme kalıbı: görünmez bağlamda çiz → görünür bağlamda drawImage(kaynak, …) ile tek komutta yapıştır. Böylece ana döngüde karmaşık yol / metin işleri yerine çoğu zaman tek bir kopya maliyeti ödersiniz — kopyanın boyutu yine piksel sayısıyla ölçülür.

Alfa kanalı ve birleştirme kuralları görünür bağlamın mevcut globalAlpha ve globalCompositeOperation ayarlarından etkilenir; katmanları birleştirirken bu ayarları bilerek sıfırlamak ( örneğin globalAlpha = 1 ve 'source-over' ) beklenmedik saydamlıkları azaltır. Bağlam maliyeti başlığında pahalı özellikler ayrı işlenmiştir — köprü kodunda varsayılanları bilinçli tutmak yeterlidir.

/**
 * @param {CanvasRenderingContext2D} screenCtx - Görünür tuval bağlamı
 * @param {HTMLCanvasElement|OffscreenCanvas} layerCanvas - Önceden çizilmiş kaynak yüzey
 * @param {number} dx
 * @param {number} dy
 */
export function stampLayer(screenCtx, layerCanvas, dx, dy) {
  screenCtx.globalAlpha = 1;
  screenCtx.globalCompositeOperation = 'source-over';
  screenCtx.drawImage(layerCanvas, dx, dy);
  screenCtx.restore();
}

Çözünürlük, DPR ve çift tampon bellek maliyeti

Görünür <canvas> CSS boyutu ile iç tampon genişliği / yüksekliği ( cihaz pikselleri ) ayrı kavramlardır; görünmez tamponda ise doğrudan iç boyutu seçersiniz. İki katman da aynı DPR ile ölçeklenmezse köşeler bulanıklaşır veya kırpma hataları oluşur. Pratik kural: statik katmanı yeniden ürettiğiniz her olayda ( örneğin pencere yeniden boyutu ) hem görünür hem görünmez tamponun cihaz piksel boyutlarını aynı politikayla güncelleyin. Yeniden boyutlandırma mantığı ile uyum bu yüzdendir.

Bellek maliyeti kabaca genişlik × yükseklik × 4 bayt ( çok kanallı tampon iç temsiline bağlı olarak değişebilir ) düzeyinde düşünülür; iki tampon yaklaşık iki kat buffer demektir. Mobil cihazlarda agresif çözünürlük, tek başına bellek baskısı ve çöp toplayıcı tetikleri doğurabilir — üst sınır ve düşürülmüş DPR politikası ürün kararıdır. Yeniden çizim maliyeti · DPR bölümü aynı gerekçeyi görünür tuval tarafında işler.

Worker içinde raster ve ImageBitmap aktarımı

Uzun süren dolgu, çok sayıda metin ölçümü veya büyük prosedürel desen ana iş parçacığını kilitleyebilir. Desteklenen ortamlarda aynı raster işi Worker içindeki bir OffscreenCanvas üzerinde yapılır; sonuç transferToImageBitmap() ile donmuş bir ImageBitmap olarak ana iş parçacığına aktarılır. Aktarımda postMessage ikinci argümanına bitmap eklenerek bellek taşınır — ana tarafta kullanım bitince bitmap.close() çağrısı kaynakları serbest bırakır.

Bu desen her projede gerekli değildir; ek karmaşıklık ( mesaj sözleşmesi, hata yüzeyi, tarayıcı uyumu ) getirir. Serileştirme veya paylaşılan bellek köprüleri burada işlenmez — odak Canvas 2D çıktısının güvenli aktarımıdır. transferControlToOffscreen ile görünür öğeyi WebGL’e devretmek farklı bir üretim hattıdır; burada yalnızca «işçi raster → bitmap → görünür drawImage» zinciri anlatılır.

Aşağıdaki örnekler minimaldir: sabit boyut ve deterministik çizim içerir; gerçek kodda iptal ( AbortSignal ), sıra numarası ve düşük öncelikli kuyruk düşünülmelidir. worker.onmessage atamasını her istekte yenilemek öğretici olsa da üretimde tek kurulum ve mesaj alanında iş kimliği daha güvenlidir.

// Worker içinde — sabit örnek çizim (harici veri yok).
self.onmessage = (event) => {
  const { width, height } = event.data || {};
  const w = Math.max(1, Math.floor(Number(width)));
  const h = Math.max(1, Math.floor(Number(height)));

  if (typeof OffscreenCanvas === 'undefined') {
    self.postMessage({ ok: false, reason: 'no-offscreen' });
    return;
  }

  const osc = new OffscreenCanvas(w, h);
  const ctx = osc.getContext('2d', { alpha: true });
  if (!ctx) {
    self.postMessage({ ok: false, reason: 'no-2d-context' });
    return;
  }

  ctx.fillStyle = '#1a2332';
  ctx.fillRect(0, 0, w, h);
  ctx.strokeStyle = '#5ec8ff';
  ctx.lineWidth = 2;
  ctx.strokeRect(8, 8, w - 16, h - 16);

  const bitmap = osc.transferToImageBitmap();
  self.postMessage({ ok: true, bitmap }, [bitmap]);
};
/**
 * @param {CanvasRenderingContext2D} visibleCtx
 * @param {Worker} worker
 * @param {{ width: number, height: number }} size
 */
export function requestRasterLayer(visibleCtx, worker, size) {
  worker.onmessage = (event) => {
    const msg = event.data || {};
    if (!msg.ok || !msg.bitmap) return;

    const bmp = msg.bitmap;
    visibleCtx.save();
    visibleCtx.setTransform(1, 0, 0, 1, 0, 0);
    visibleCtx.globalAlpha = 1;
    visibleCtx.globalCompositeOperation = 'source-over';
    visibleCtx.drawImage(bmp, 0, 0);
    visibleCtx.restore();

    bmp.close();
  };

  worker.postMessage(size);
}

Tekrar kullanım senaryoları ve katman düzeni

Yaygın profesyonel kullanımlar: düşük frekansla değişen kullanıcı arayüzü etiketleri için küçük ara tampon, dünya özetinin bir kez çizilip üzerine dinamik öğelerin eklenmesi, karmaşık ama seyrek güncellenen desenlerin önbake edilmesi. Bu rol dağılımı, statik / dinamik katman ayrımı ile birebir örtüşür; görünmez tampon yalnız araçtır, mimari karar «neyi ne sıklıkla yenilersiniz?» sorusuna bağlıdır.

Varlık önbelleği ve görsel yükleme zaman çizelgesi Varlık önbelleği ve görsel yükleme başlıklarında işlenir; burada yinelemenin anlamı yok — köprü şudur: decode edilmiş görseller zaten bitmap kaynak olarak drawImage ile kullanılabilir; görünmez tampon ise çok adımlı bileşik sahneyi tek dokuda toplamak için kullanılır.

Çağrı sırasını düzenlemek ( önce opak katmanlar, sonra saydam ) ana iş parçacığında bile sürtünmeyi düşürür; Batching mantığı bu düzeni üst düzeyde tamamlar — işçi tampon onun yerine geçmez, tamamlayıcıdır.

Sınırlar, senkron ve sık düşülen varsayımlar

Görünmez tampon «GPU hızlandırması garantisi» değildir; raster işin doğası gereği piksel işlersiniz. Tam boy bitmap her kare kopyalanıyorsa ana iş parçacığında yine ağır bir drawImage ödersiniz — işçi tarafındaki kazanımı sıfırlayabilir veya gecikmeyi «bir kare geriden» hissedilir yapabilir.

Worker çıktısı ile kullanıcı etkileşimi arasında tek kare gecikme sıradandır; imleç takibi gibi düşük gecikme gerektiren etkileşimleri doğrudan ana bağlamda tutmak daha güvenlidir. Ayrıca her ortamda transferToImageBitmap veya 2D worker bağlamı aynı davranmayabilir — özellik projeye küçük bir uyumluluk tablosu ( hedef tarayıcı sürümleri ) çıkarılmalıdır.

Bellek sızıntısı riski: aktarılan her ImageBitmap için eşleşen bir close() politikası yoksa bellek büyümeye devam eder. Üretimde bütçe ve üst sınır ( eşzamanlı kaç işçi işi ) açık yazılmalıdır. Yeniden çizim bayrağı ile birleştirildiğinde «değişmedi» dalında işçi işi tetiklenmez — gereksiz bitmap üretimi kesilir.

Bu sayfanın sınırı

WebGPU compute, WebGL bağlam devri ve üç boyutlu iş hattı burada ele alınmaz. Odak: Canvas 2D ile üretilen rasterın görünmez tampon ve işçi iş parçacığı üzerinden güvenli aktarımı ile ürün düzeyinde maliyet / bellek dengesidir.

  • Tampon boyutu ürün üst sınırıyla sabitlendi mi ( kötümser ölçüm koruması)?
  • Görünür ve görünmez yüzeyler aynı DPR politikasını paylaşıyor mu?
  • Aktarılan her ImageBitmap için close() yolu tanımlandı mı?
  • Tam boy kopya hâlâ kare bütçesini aşıyorsa strateji gözden geçirildi mi?
  • İşçi raster ile etkileşim gecikmesi ürün için kabul edilebilir mi?