HTML5 Canvas · Performans ve optimizasyon
Batching mantığı: bağlam sıçramadan kaçınmak ve komutları gruplamak
Canvas 2D hemen çizilen (
immediate mode ) bir API’dir; her çizgi ve dolgu çağrısı bir süreç
olarak görülürken, bağlam üzerindeki özellikler (
fillStyle,
strokeStyle,
globalCompositeOperation,
filter,
shadowBlur … ) sık sık değiştiğinde «bir komut çizmek» bile görünürde ucuz olsa
bile üretimde toplam kare süresi şişebilir.
Batching, çizim çağrılarını aynı bağlam durumunu paylaşan gruplara
ayırıp sıralamayı bilerek seçmektir. Piksel başına ödenen fiyat ve gereksiz tam redraw soruları
Yeniden çizim maliyeti ile işlenir; bu
sayfa özellikle «aynı turda çok benzer çizimi daha ucuz nasıl dökerim?» sorusuna odaklanır.
Güncelleme ayrımı ve tek doğru çizim sırası disiplini Update vs render ile, temizlik ve yeniden çiz akışı Temizle ve yeniden çiz ile birlikte okunmalıdır. Tahsis azaltma için komşu Nesne havuzlama; görünmez tampon için Offscreen Canvas başlığı düşünülür.
Özet: hangi desenlerde işe yarar?
| Durum | Batching fırsatı | Dikkat |
|---|---|---|
| Aynı dolgu / kontur stili ile yüzlerce primitive | Tek fillStyle ataması ile uzun tur |
Z sırası ve saydamlık bozulmasın |
| Aynı sprite levhasından çok kesit | drawImage çağrıları art arda |
Atlas düzeni ve kaynak referansı sabit |
| Ağır süzgeç veya gölge «modları» | Mod başına az «geçiş», sonra toplu çizim | Pahalı modların kapsamı dar tutulmalı ( bağlam maliyeti ) |
Batching nedir ve bağlam sıçraması
Pratik tanım: bir kare içinde çizeceğiniz öğeleri, tarayıcıya mümkün olduğunca az sayıda bağlam geçişiyle iletmek. Bağlam geçişi burada soyut bir terimdir — örneğin dolgu rengini her üçgende bir değiştirmek ile yüz üçgende bir kez değiştirmek arasındaki fark; kesin iç maliyet tarayıcıya göre değişir, fakat profilde sık görülen «ani sıçramalar» çoğu zaman hem piksel işinden hem durum karmaşıklığından kaynaklanır. Piksel bütçesi ve pahalı özellikler ayrı başlıklarda işlendiği için burada tekrar ölçüm tekniği anlatılmaz — özetle batching, bu iki başlığın üzerine inşa edilen çağrı sırası mühendisliğidir.
Batching «otomatik olarak etkinleşmez»; kod tarafında bir küçük iş listesi veya sıralama katmanı gerektirir. Az sayıda sprite ile başlayan prototipte kazanç doğrusal görünmeyebilir; yüzlerce parçacık, çok katmanlı kullanıcı arayüzü veya harita döşemesi gibi durumlarda ise sıra seçimi ürün yapımına dönüşür. Bu yüzden önce ölçüm, sonra sıralama politikası — erken mikro optimizasyon yapmadan önce darboğazın gerçekten bağlam mı yoksa ham piksel mi olduğunu ayırt etmek gerekir.
save /
restore blokları düzenli kullanıldığında okunabilirlik artar; her öğede iç içe
derin yığın ise hem maliyet hem hata riski üretir.
Anti-kalıplar listesindeki
«her varlıkta derin yığın» uyarısı ile uyumludur — batching, gereksiz
save/restore döngüsünün yerine geçmez; bunları sığ ve
öngörülebilir
tutmak gerekir.
Durum anahtarı üretimi ve sıralama politikası
Gruplama için önce her çizilebilir öğeye bir sıralama anahtarı üretilir. Tipik bileşenler: doku veya levha kimliği ( tamsayı ), dolgu rengi veya gradient imzası ( dize ), birleştirme modu ( dize ), küresel saydamlık ( kuantize edilmiş sayı ). Anahtarın amacı «aynı bağlam ayarıyla çizilebilecekleri yan yana getirmek»tir; doğru seçilmiş anahtar, sıralamanın birinci geçişinde en büyük «sıfır maliyetli» blokları oluşturur.
İkinci öncelik çoğu oyunda derinlik (
zIndex ) olur — ancak saydam öğeler üst üste bindiğinde yalnızca texture +
stile göre sıralamak görsel olarak yanlış sonuç verebilir; bu uzlaşma
beşinci bölümde açılır. Şimdilik şunu
sabitleyin: anahtar tasarımı, ürünün doğru görünmesine bağlı olarak en az iki geçişli (
önce opaklar, sonra saydam grup içinde Z ) olabilir.
Aşağıdaki örnek deterministik bir sıralama üretir; dış girdi kullanmaz ve karşılaştırıcılar Unicode kod birimlerine göre dize sırası uygular — küçük ve orta ölçekli listeler için yeterlidir. Çok büyük sahnelerde sayısal anahtar ile tek geçişli radix benzeri yaklaşımlar düşünülebilir; bu sayfa veri yapısı seçimini ürün kararı olarak bırakır.
/**
* @typedef {{
* textureId: number,
* fillStyle: string,
* composite: string,
* alphaKey: number,
* z: number
* }} BatchSortable
*/
/** Tupla sırası: doku → dolgu → birleştirme → saydamlık kuşağı → Z */
export function compareBatchKeys(a, b) {
if (a.textureId !== b.textureId) return a.textureId - b.textureId;
const fs = String(a.fillStyle).localeCompare(String(b.fillStyle));
if (fs !== 0) return fs;
const co = String(a.composite).localeCompare(String(b.composite));
if (co !== 0) return co;
if (a.alphaKey !== b.alphaKey) return a.alphaKey - b.alphaKey;
return a.z - b.z;
}
/** Mutasyon yapmadan yeni sıralı dizi döndürür. */
export function sortForBatching(items) {
return [...items].sort(compareBatchKeys);
}
drawImage yoğunluğu ve atlas grupları
Aynı bitmap kaynağından (
HTMLImageElement,
ImageBitmap, başka bir tuval ) yapılan ardışık
drawImage çağrıları genelde «aynı dokuya dokunan» bir grubu temsil eder. Sprite
levhası (
sprite sheet ) kullanıyorsanız textureId mantıksal
olarak tek kalır; kesit koordinatları (
sx, sy, sw, sh ) çağrı başına değişir. Bu yapı,
Sprite
levhası
konusunun performans yüzüdür — levha düzeni burada tekrar edilmez.
Kaynak decode maliyeti çizim döngüsünden önce tamamlanmış olmalıdır; yarı yüklenmiş görselle çağrı yapmak hem doğruluk hem zamanlama sorunu doğurur. Görsel yükleme akışı ile uyum bu yüzdendir. Batching yalnızca «zaten hazır bitmap» varsayımıyla anlamlıdır.
Çoklu atlas kullanıyorsanız sıralama önce atlas kimliğine göre yapılır; böylece önbellek ve dahili hazırlık için daha az sıçrama elde edilir — kesin kazanım yine profille doğrulanır.
beginPath birimi ve kontur toplu işleme
Yol tabanlı çizimde (
moveTo,
lineTo,
arc,
bezierCurveTo … ) her yeni şekil için beginPath() ile kontur
birimini sıfırlamak gerekir; aksi halde önceki konturlar dolgu veya kontur ile yanlışlıkla
birleşir. Bu teknik gereklilik, «tek dev yol içinde yüzlerce şekil» yaklaşımıyla çelişebilir
— profesyonel kod genelde vektörleri mantıksal olarak gruplar ve her mantıksal şekil için
net bir beginPath çifti kullanır.
Toplu dolgu rengine oturan çok sayıda dikdörtgen örneği: önce fillStyle bir kez
atanır, sonra her biri için kısa bir beginPath +
rect +
fill üçlüsü çalıştırılır. Kontur genişliği ve çizgi bağlantısı (
lineJoin,
miterLimit ) paylaşılıyorsa yine tek atama + çoklu yol birimi modeli uygundur.
Detaylı bağlam sırası
2D bağlam
girişindeki düşünceyle uyumludur.
Metin çizimi (
fillText ) çoğu arayüzde düşük frekanslıdır; her karede binlerce dinamik etiket
batching ile bile pahalı kalır — bu durumda önbake tampon veya katman (
Offscreen ·
senaryolar
) genellikle daha doğru araçtır; batching tek başına mucize değildir.
Derinlik sırası ile gruplamanın uzlaştırılması
Resimleyici düzeni ( painter’s algorithm ): önce arkayı, sonra önü çizersiniz. Stile göre sıralamak, öğeler üst üste bindiğinde bu sırayı bozabilir; özellikle kısmi saydamlıkta iki sprite doğru görünüm için belirli bir sırada çizilmek zorunda olabilir. Çözüm setleri: ( a ) sahneyi iki geçişte çizmek — önce tam opak grubu texture/stile göre batch’lenir, sonra saydam grubun içinde Z korunur; ( b ) sorunlu bölgeleri ayrı küçük katmana çıkarmak ( katman tuval ); ( c ) düşük öğe sayısında batching’i bırakıp doğru sırayı korumak.
Hangi politikanın seçileceği tamamen görsel gereksinime bağlıdır; batching «doğru görüntüyü» feda etmemelidir. Üretimde tipik uzlaşma: çok sayıda opak dünya döşemesi + az sayıda saydam varlık — ilk grup agresif şekilde gruplanır, ikinci grup daha küçük ve sıraya duyarlı kalır.
Parçacık sistemlerinde Z sırası bazen görsel olarak daha az kritiktir; yine de additive ( additive ) karışım kullanılıyorsa sıra doğrudan parlaklığı etkiler. Bu yüzden anahtar tasarımına birleştirme modunu dahil etmek şarttır — aynı moddaki partikül kolayca gruplanır, mod değiştikçe yeni parti başlatılır.
Komut listesi flush döngüsü ve durum önbelleği
Kare başında çizilecek öğeler bir diziye (
veya hafif yapıya ) yazılır; sıralama uygulanır; ardından tek bir «flush» döngüsü bağlamı
günceller ve çizer. Flush içinde «son kullanılan» değerleri yerel değişkenlerde tutmak,
gereksiz atamaları (
aynı fillStyle’ı tekrar yazmak gibi ) keser. Bu desen,
havuzlama ile birlikte düşünülür:
liste düğümleri yeniden kullanılabilir; burada havuz ayrıntısı verilmez.
Örnek, dikdörtgen dolgu ve kesitli drawImage için sınırlı bir alt küme
gösterir; üretimde komut türü enum’u ve doğrulama (
pozitif boyutlar, sonlu koordinatlar ) eklenmelidir. Özellikle kullanıcı veya ağ verisinden
gelen sayılar doğrudan çizime bağlanmamalıdır — üst sınır ve Number.isFinite
kontrolü güvenli kod için standarttır.
drawImage satırları sıralama anahtarında yer almak için ortak alanları
taşıyabilir; fillStyle bağlamı ise örnekte yalnızca fillRect
dalında güncellenir.
import { sortForBatching } from './sort-draw-batch.js';
/**
* @param {CanvasRenderingContext2D} ctx
* @param {Array<{
* textureId: number,
* fillStyle: string,
* composite: string,
* alphaKey: number,
* z: number,
* kind: 'fillRect',
* x: number, y: number, w: number, h: number
* } | {
* textureId: number,
* fillStyle: string,
* composite: string,
* alphaKey: number,
* z: number,
* kind: 'drawImage',
* image: CanvasImageSource,
* sx: number, sy: number, sw: number, sh: number,
* dx: number, dy: number, dw: number, dh: number
* }>} cmds
*/
export function flushBatched(ctx, cmds) {
const sorted = sortForBatching(cmds);
let curFill = '';
let curComp = '';
let curAlphaKey = NaN;
ctx.save();
for (const c of sorted) {
if (c.composite !== curComp) {
curComp = c.composite;
ctx.globalCompositeOperation = c.composite;
}
if (c.alphaKey !== curAlphaKey) {
curAlphaKey = c.alphaKey;
ctx.globalAlpha = c.alphaKey / 255;
}
if (c.kind === 'fillRect') {
if (c.fillStyle !== curFill) {
curFill = c.fillStyle;
ctx.fillStyle = c.fillStyle;
}
ctx.fillRect(c.x, c.y, c.w, c.h);
} else if (c.kind === 'drawImage') {
ctx.drawImage(c.image, c.sx, c.sy, c.sw, c.sh, c.dx, c.dy, c.dw, c.dh);
}
}
ctx.restore();
}
Ölçüm, tuzaklar ve kontrol listesi
Batching her zaman hızlandırmaz: küçük listelerde sıralama maliyeti ve ekstra bellek trafiği kazancı gölgeler; çok küçük sahne için doğrudan çizim daha sade kalır. Profilde net iyileşme görmüyorsanız sıralama anahtarını sadeleştirin veya iki geçişli politikayı gözden geçirin — karmaşıklık borcu geri döner.
Dinamik anahtar dizeleri (
her kare yeni fillStyle örneği tahsis etmek ) çöp toplayıcı yükü oluşturur;
sabit palet veya önbelleğe alınmış stil nesneleri tercih edilir. Bu husus
havuzlama ile örtüşür ancak bu
sayfada uygulama detayı verilmez.
«Her şeyi tek buffer’da çizmek» bazen kirli dikdörtgen stratejisiyle çelişir — tam ekran flush ile kısmi güncelleme kazancını sıfırlamayın. Ürün kararı hangi optimizasyon ekseninin öncelikli olduğunu belirler; ikisi birlikte seçilirken önce sahne yapısı çizilir.
Bu sayfanın sınırı
WebGL / WebGPU komut kuyruğu, derleyici ve sürücü düzeyinde otomatik batching burada işlenmez. Odak: Canvas 2D bağlamında uygulama tarafı sıra seçimi ve güvenli flush düzenidir.
- Sıralama anahtarı görsel Z ve birleştirme kurallarıyla çelişiyor mu?
- Pahalı bağlam özellikleri dar ve öngörülebilir bloklarda mı?
- Aynı atlas kimliği ile gerçekten gruplanabilecek kaç çağrı var ( profil )?
- Gereksiz
save/restoreve dinamik stil tahsisi azaltıldı mı? - Kısmi redraw politikası tam ekran flush ile çarpışıyor mu?