Giriş
Apple'ın denemesi olarak başlayan HTML5 kanvası, web'de 2D anında mod grafikleri için en yaygın olarak desteklenen standarttır. Birçok geliştirici artık çok çeşitli multimedya projeleri, görselleştirmeler ve oyunlar için bu aracı kullanıyor. Ancak geliştirdiğimiz uygulamaların karmaşıklığı arttıkça geliştiriciler istemeden performans duvarına çarpıyor. Tuval performansını optimize etmeyle ilgili birçok bilgi vardır. Bu makalenin amacı, söz konusu gövdenin bir kısmını geliştiriciler için daha kolay anlaşılır bir kaynakta birleştirmektir. Bu makalede, tüm bilgisayar grafikleri ortamları için geçerli olan temel optimizasyonların yanı sıra kanvas uygulamaları geliştikçe değişikliğe tabi olan kanvasa özgü teknikler yer almaktadır. Özellikle tarayıcı tedarikçileri tuval GPU hızlandırmasını uyguladıkça, açıklanan performans tekniklerinden bazıları muhtemelen daha az etkili hale gelecektir. Bu durum, uygun durumlarda belirtilir. Bu makalede HTML5 kanvası kullanımı ele alınmamaktadır. Bunun için HTML5Rocks'taki tuval ile ilgili makalelere, Dive into HTML5 sitesindeki bu bölüme veya MDN Canvas eğitim makalesine göz atın.
Performans testi
JSPerf (jsperf.com) testleri, HTML5 tuvalinin hızla değişen dünyasına ayak uydurmak için önerilen her optimizasyonun hâlâ çalıştığını doğrular. JSPerf, geliştiricilerin JavaScript performans testleri yazmasına olanak tanıyan bir web uygulamasıdır. Her test, ulaşmaya çalıştığınız bir sonuca (ör. tuvali temizleme) odaklanır ve aynı sonuca ulaşan birden fazla yaklaşım içerir. JSPerf, her yaklaşımı kısa bir süre içinde mümkün olduğunca çok sayıda çalıştırır ve saniye başına istatistiksel olarak anlamlı bir yineleme sayısı sağlar. Yüksek puanlar her zaman daha iyidir. JSPerf performans test sayfasını ziyaret edenler, testi tarayıcılarında çalıştırabilir ve JSPerf'in normalleştirilmiş test sonuçlarını Browserscope'ta (browserscope.org) depolamasına izin verebilir. Bu makaledeki optimizasyon teknikleri bir JSPerf sonucuyla desteklendiğinden, tekniğin hâlâ geçerli olup olmadığıyla ilgili güncel bilgileri görmek için bu sayfaya geri dönebilirsiniz. Bu sonuçları grafik olarak gösteren ve bu makalenin içine yerleştirilmiş küçük bir yardımcı uygulama yazdım.
Bu makaledeki tüm performans sonuçları, tarayıcı sürümüne göre sınıflandırılmıştır. Tarayıcıda hangi işletim sisteminin çalıştığını veya daha da önemlisi, performans testi çalıştırıldığında HTML5 kanvasına donanım hızlandırma uygulanıp uygulanmadığını bilmediğimiz için bu bir sınırlamadır. Chrome'un HTML5 kanvası donanım hızlandırmalı olup olmadığını öğrenmek için adres çubuğundan about:gpu
adresini ziyaret edebilirsiniz.
Ekran dışı bir tuvalde önceden oluştur
Oyun yazarken genellikle olduğu gibi, birden fazla karede ekrana benzer primitifleri yeniden çiziyorsanız sahnenin büyük bölümlerini önceden oluşturarak önemli performans kazanabilirsiniz. Ön oluşturma, geçici görüntülerin oluşturulacağı ayrı bir ekran dışı kanvas (veya kanvaslar) kullanmak ve ardından ekran dışı kanvasları görünür kanvasa geri oluşturmak anlamına gelir. Örneğin, Mario'yu saniyede 60 kare hızında yeniden çizdiğinizi varsayalım. Şapka, bıyık ve "M" harflerini her karede yeniden çizebilir veya animasyonu çalıştırmadan önce Mario'yu önceden oluşturabilirsiniz. Önceden oluşturma yok:
// canvas, context are defined
function render() {
drawMario(context);
requestAnimationFrame(render);
}
önceden işleme:
var m_canvas = document.createElement('canvas');
m_canvas.width = 64;
m_canvas.height = 64;
var m_context = m_canvas.getContext('2d');
drawMario(m_context);
function render() {
context.drawImage(m_canvas, 0, 0);
requestAnimationFrame(render);
}
requestAnimationFrame
değerinin kullanımına dikkat edin. Bu konu daha sonraki bir bölümde daha ayrıntılı olarak ele alınacaktır.
Bu teknik, özellikle oluşturma işlemi (yukarıdaki örnekte drawMario
) pahalı olduğunda etkilidir. Bunun iyi bir örneği, çok pahalı bir işlem olan metin oluşturmadır.
Ancak "önceden oluşturulmuş gevşek" test senaryosunun performansı düşük. Önizleme yaparken geçici kanvasınızın çizdiğiniz resmin etrafına tam olarak sığdığından emin olmanız önemlidir. Aksi takdirde, ekran dışı oluşturmanın sağladığı performans kazancı, bir büyük kanvasın diğerine kopyalanmasıyla ortaya çıkan performans kaybıyla dengelenir (kaynak hedef boyutuna göre değişir). Yukarıdaki testte rahat bir tuval daha küçüktür:
can2.width = 100;
can2.height = 40;
Daha düşük performans sağlayan gevşek bağlantıya kıyasla:
can3.width = 300;
can3.height = 100;
Kanvas çağrılarını gruplandırma
Çizim pahalı bir işlem olduğundan, çizim durum makinesini uzun bir komut grubuyla yükleyip tümünü video arabelleğine aktarması daha verimlidir.
Örneğin, birden fazla çizgi çizerken tüm çizgileri içeren bir yol oluşturmak ve bunu tek bir çizim çağrısıyla çizmek daha verimlidir. Diğer bir deyişle, ayrı çizgiler çizmek yerine:
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i+1];
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.stroke();
}
Tek bir çoklu çizgi çizerek daha iyi performans elde ederiz:
context.beginPath();
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i+1];
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
}
context.stroke();
Bu, HTML5 tuval dünyası için de geçerlidir. Örneğin, karmaşık bir yol çizerken segmentleri ayrı ayrı oluşturmak yerine tüm noktaları yola yerleştirmek daha iyidir (jsperf).
Ancak Canvas'ta bu kuralın önemli bir istisnası olduğunu unutmayın: İstenilen nesnenin çizilmesiyle ilgili primitiflerin küçük sınır kutuları varsa (örneğin, yatay ve dikey çizgiler) bunları ayrı ayrı oluşturmak daha verimli olabilir (jsperf).
Tuval durumundaki gereksiz değişikliklerden kaçınma
HTML5 tuval öğesi, geçerli yolu oluşturan önceki noktaların yanı sıra dolgu ve fırça stilleri gibi şeyleri izleyen bir durum makinesinin üzerine uygulanır. Grafik performansını optimize etmeye çalışırken yalnızca grafik oluşturmaya odaklanmak cazip gelebilir. Ancak durum makinesini değiştirmek, performans açısından da ek yük getirebilir. Örneğin, bir sahneyi oluşturmak için birden fazla dolgu rengi kullanırsanız zemine yerleştirme yerine renge göre oluşturma işlemi daha ucuz olur. İnce çizgili bir desen oluşturmak için bir çizgi oluşturabilir, renkleri değiştirebilir, sonraki çizgiyi oluşturabilirsiniz.
for (var i = 0; i < STRIPES; i++) {
context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
context.fillRect(i * GAP, 0, GAP, 480);
}
Alternatif olarak, tüm tek şeritleri ve ardından tüm çift şeritleri oluşturabilirsiniz:
context.fillStyle = COLOR1;
for (var i = 0; i < STRIPES/2; i++) {
context.fillRect((i*2) * GAP, 0, GAP, 480);
}
context.fillStyle = COLOR2;
for (var i = 0; i < STRIPES/2; i++) {
context.fillRect((i*2+1) * GAP, 0, GAP, 480);
}
Beklendiği gibi, durum makinesinin değiştirilmesi pahalı olduğundan aralıklı yaklaşım daha yavaştır.
Yeni durumun tamamını değil, yalnızca ekrandaki farklılıkları oluşturma
Beklendiği gibi, ekranda daha az oluşturma işlemi yapmak daha fazla oluşturma işleminden daha ucuzdur. Yeniden çizimler arasında yalnızca artımlı farklılıklar varsa yalnızca farkı çizerek önemli bir performans artışı elde edebilirsiniz. Başka bir deyişle, çizmeden önce tüm ekranı temizlemek yerine:
context.fillRect(0, 0, canvas.width, canvas.height);
Çizilen sınırlayıcı kutuyu takip edin ve yalnızca bunu temizleyin.
context.fillRect(last.x, last.y, last.width, last.height);
Bilgisayar grafikleri hakkında bilginiz varsa bu tekniği "yeniden çizme bölgeleri" olarak da biliyor olabilirsiniz. Bu teknikte, önceden oluşturulan sınırlayıcı kutu kaydedilir ve her oluşturma işleminde temizlenir. Bu teknik, JavaScript Nintendo emülatör konuşmasında gösterildiği gibi piksel tabanlı oluşturma bağlamları için de geçerlidir.
Karmaşık sahneler için birden fazla katmanlı kanvas kullanın
Daha önce de belirtildiği gibi, büyük resimler çizmek pahalıdır ve mümkünse bundan kaçınılmalıdır. Ön oluşturma bölümünde gösterildiği gibi, ekranın dışı oluşturmak için başka bir tuval kullanmaya ek olarak, birbiri üzerine katmanlı olarak kanvaslar da kullanabiliriz. Ön plan kanvaslarında saydamlık kullanarak, oluşturma sırasında alfa değerlerini bir araya getirmek için GPU'dan yararlanabiliriz. Bunu, birbirinin üzerine yerleştirilmiş iki tam konumlandırılmış kanvasla aşağıdaki gibi ayarlayabilirsiniz.
<canvas id="bg" width="640" height="480" style="position: absolute; z-index: 0">
</canvas>
<canvas id="fg" width="640" height="480" style="position: absolute; z-index: 1">
</canvas>
Burada tek bir kanvas kullanmanın avantajı, ön plan kanvasımıza çizim yaptığımızda veya kanvası temizlediğimizde arka planı hiçbir zaman değiştirmememizdir. Oyununuz veya multimedya uygulamanız ön plan ve arka plana bölünebiliyorsa önemli bir performans artışı elde etmek için bunları ayrı tuvallerde oluşturmayı düşünebilirsiniz.
İnsanların kusurlu algısından yararlanarak arka planı yalnızca bir kez veya ön plana kıyasla daha yavaş bir hızda oluşturabilirsiniz (kullanıcınızın dikkatinin büyük bir kısmını ön planın kaplaması muhtemeldir). Örneğin, her oluşturma işleminde ön planı oluşturabilir ancak arka planı yalnızca her N. karede oluşturabilirsiniz. Ayrıca, uygulamanız bu tür bir yapıyla daha iyi çalışıyorsa bu yaklaşımın herhangi bir sayıda kompozit zemin için iyi genelleşeceğini de unutmayın.
shadowBlur özelliğini kullanmaktan kaçının
Diğer birçok grafik ortamında olduğu gibi HTML5 kanvası da geliştiricilerin primitifleri bulanıklaştırmasına olanak tanır ancak bu işlem çok pahalı olabilir:
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = 'rgba(255, 0, 0, 0.5)';
context.fillRect(20, 20, 150, 100);
Kanvası temizlemenin çeşitli yollarını öğrenin
HTML5 kanvası bir anında mod çizim paradigması olduğundan sahnenin her karede açıkça yeniden çizilmesi gerekir. Bu nedenle, kanvası temizlemek HTML5 kanvas uygulamaları ve oyunları için temel olarak önemli bir işlemdir.
Tuval durum değişikliklerinden kaçının bölümünde belirtildiği gibi, tuvalin tamamını temizlemek genellikle istenmez ancak bunu yapmanız gerekirse iki seçenek vardır: context.clearRect(0, 0, width, height)
çağrısı yapmak veya bunu yapmak için tuvale özgü bir hile kullanmak:canvas.width = canvas.width
. Bu makalenin yazıldığı sırada clearRect
genellikle genişliği sıfırlama sürümünden daha iyi performans gösteriyor ancak bazı durumlarda Chrome 14'te canvas.width
sıfırlama hilesi kullanmak önemli ölçüde daha hızlıdır.
Bu ipucu, temel kanvas uygulamasına büyük ölçüde bağlı olduğundan ve çok fazla değişiklik gösterebileceğinden dikkatli olun. Daha fazla bilgi için Simon Sarris'in tuvali temizlemeyle ilgili makalesine bakın.
Kayan nokta koordinatlarından kaçının
HTML5 kanvas alt piksel oluşturmayı destekler ve bu özelliği devre dışı bırakmanın bir yolu yoktur. Tam sayı olmayan koordinatlarla çizerseniz çizgileri yumuşatmaya çalışmak için otomatik olarak kenar yumuşatmayı kullanır. Seb Lee-Delisle tarafından yazılan bu piksel altı kanvas performansı makalesinde yer alan görsel efekt aşağıda verilmiştir:
İstediğiniz efekt pürüzsüz sprite değilse Math.floor
veya Math.round
(jsperf) kullanarak koordinatlarınızı tam sayılara dönüştürmek çok daha hızlı olabilir:
Kayan noktalı koordinatlarınızı tam sayılara dönüştürmek için birkaç akıllıca teknik kullanabilirsiniz. Bunlardan en iyi performans göstereni, hedef sayıya bir yarısını eklemeyi ve ardından kesirli kısmı ortadan kaldırmak için sonuç üzerinde bit işlemleri gerçekleştirmeyi içerir.
// With a bitwise or.
rounded = (0.5 + somenum) | 0;
// A double bitwise not.
rounded = ~~ (0.5 + somenum);
// Finally, a left bitwise shift.
rounded = (0.5 + somenum) << 0;
Performans dökümünün tamamını burada bulabilirsiniz (jsperf).
Tuval uygulamaları, tam sayı olmayan koordinatları hızlı bir şekilde oluşturabilecek olan GPU ile hızlandırıldığında bu tür optimizasyonun artık önemli olmaması gerektiğini unutmayın.
requestAnimationFrame
ile animasyonlarınızı optimize etme
Tarayıcıda etkileşimli uygulamaları uygulamanın önerilen yolu, nispeten yeni requestAnimationFrame
API'dir. Tarayıcıya belirli bir sabit saniye aralığında oluşturma komutu vermek yerine, tarayıcıdan oluşturma rutininizi çağırmasını ve tarayıcı müsait olduğunda çağrılmasını kibarca istersiniz. Bunun güzel bir yan etkisi olarak, sayfa ön planda
değilse tarayıcı, sayfa ön planda görüntülenmeyecek kadar akıllıdır.
requestAnimationFrame
geri çağırma işlevi, 60 FPS geri çağırma hızını hedefler ancak bunu garanti etmez. Bu nedenle, son oluşturma işleminden bu yana ne kadar süre geçtiğini takip etmeniz gerekir. Bu, aşağıdaki gibi görünebilir:
var x = 100;
var y = 100;
var lastRender = Date.now();
function render() {
var delta = Date.now() - lastRender;
x += delta;
y += delta;
context.fillRect(x, y, W, H);
requestAnimationFrame(render);
}
render();
requestAnimationFrame
'ün bu kullanımının, kanvasın yanı sıra WebGL gibi diğer oluşturma teknolojileri için de geçerli olduğunu unutmayın.
Bu API, şu anda yalnızca Chrome, Safari ve Firefox'ta kullanılabildiğinden bu dolguyu kullanmanız gerekir.
Çoğu mobil kanvas uygulaması yavaştır
Mobil cihazlardan bahsedelim. Maalesef bu makalenin yazıldığı sırada yalnızca Safari 5.1'i çalıştıran iOS 5.0 beta sürümünde GPU hızlandırmalı mobil kanvas uygulaması mevcuttur. GPU hızlandırması olmadan mobil tarayıcılar genellikle modern kanvas tabanlı uygulamalar için yeterince güçlü CPU'lara sahip değildir. Yukarıda açıklanan JSPerf testlerinin birçoğu, mobil cihazlarda masaüstüne kıyasla çok daha kötü performans gösterir. Bu da başarılı bir şekilde çalıştırmayı bekleyebileceğiniz cihazlar arası uygulama türlerini büyük ölçüde kısıtlar.
Sonuç
Özetlemek gerekirse bu makalede, performanslı HTML5 kanvas tabanlı projeler geliştirmenize yardımcı olacak kapsamlı bir dizi yararlı optimizasyon tekniği ele alındı. Yeni bilgiler edindiğinize göre, harika içeriklerinizi optimize etmeye başlayın. Şu anda optimize edilecek bir oyununuz veya uygulamanız yoksa ilham almak için Chrome Experiments ve Creative JS'ye göz atın.
Referanslar
- Anında modu ve saklanan modu.
- Diğer HTML5Rocks tuval makaleleri.
- HTML5'e Giriş başlıklı makalenin Canvas bölümü.
- JSPerf, geliştiricilerin JS performans testleri oluşturmasına olanak tanır.
- Browserscope, tarayıcı performansı verilerini depolar.
- JSPerfView, JSPerf testlerini grafik olarak oluşturur.
- Simon'un tuvali temizlemeyle ilgili blog yayınını ve tuval performansıyla ilgili bölümler içeren HTML5 Unleashed kitabını inceleyin.
- Sebastian'ın alt piksel oluşturma performansıyla ilgili blog yayınını inceleyin.
- Ben'in JS NES emülatörünü optimize etmeyle ilgili konuşması.
- Chrome Geliştirici Araçları'ndaki yeni tuval profilleyici.