HTML5 Tuval performansını iyileştirme

Boris Smus
Boris Smus

Giriş

Apple'ın bir deneyi olarak başlayan HTML5 tuval, web'de 2D hemen modlu grafikler için en yaygın şekilde desteklenen standarttır. Birçok geliştirici artık çok çeşitli multimedya projeleri, görsel öğeler ve oyunlar için bunu kullanıyor. Ancak geliştirdiğimiz uygulamaların karmaşıklığı arttıkça geliştiriciler istemeden performans duvarıyla çarptı. Tuval performansını optimize etme konusunda pek çok bilgi vardır. Bu makale, bu gövdenin bir kısmını geliştiriciler için daha kolay anlaşılır bir kaynakta birleştirmeyi amaçlamaktadır. Bu makalede, tüm bilgisayar grafiği ortamları için geçerli olan temel optimizasyonların yanı sıra tuval uygulamaları iyileştikçe değişime tabi olan tuvale özgü teknikler yer almaktadır. Özellikle tarayıcı tedarikçileri tuval GPU hızlandırmasını uyguladıkça, tartışılan performans tekniklerinden bazılarının etkisi büyük olasılıkla daha az olur. Gerektiğinde bu durum belirtilir. Bu makalenin HTML5 zemininin kullanımıyla ilgili olmadığını unutmayın. Bunun için HTML5Rocks ile ilgili bu tuvallerle ilgili makalelere, HTML5'i Öğrenme hakkındaki bu bölüme veya MDN Tuvali'ne göz atın. eğitim.

Performans testi

HTML5 tuvalinin hızla değişen dünyasına hitap etmek için JSPerf (jsperf.com) testleri, ö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 odaklanır (örneğin, zemini temizleme) ve aynı sonuca ulaşan birden fazla yaklaşımı içerir. JSPerf, kısa bir süre boyunca her yaklaşımı mümkün olduğunca çok kez çalıştırır ve saniye başına istatistiksel olarak anlamlı bir yineleme sayısı sunar. Yüksek puanlar her zaman daha iyidir. Bir 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 yedeklendiğinden, tekniğin hâlâ geçerli olup olmadığıyla ilgili güncel bilgileri görmek için geri dönebilirsiniz. Bu sonuçları bu makaleye yerleştirilmiş şekilde grafik olarak oluşturan küçük bir yardımcı uygulama yazdım.

Bu makaledeki tüm performans sonuçları, tarayıcı sürümünde anahtarlanmıştır. Tarayıcının hangi işletim sisteminde çalıştığını, daha da önemlisi, performans testi çalıştırıldığında HTML5 kanvasının donanım hızlandırmalı olup olmadığını bilmediğimizden bu durum bir sınırlama olarak ortaya çıkar. Adres çubuğunda about:gpu adresini ziyaret ederek Chrome'un HTML5 kanvasının donanım hızlandırmalı olup olmadığını öğrenebilirsiniz.

Ekran dışı bir tuvale önceden oluşturma

Benzer temel öğeleri ekrana birden fazla karede yeniden çiziyorsanız (oyun yazarken genellikle yaşandığı gibi), sahnenin büyük bölümlerini önceden oluşturarak performanstan büyük kazanç elde edebilirsiniz. Önceden oluşturma, üzerinde geçici resimler oluşturmak için ayrı bir ekran dışı tuval (veya tuval) kullanmak ve ardından ekran dışı tuvalleri tekrar görünür durumda oluşturmak anlamına gelir. Örneğin, Mario'yu saniyede 60 kare hızla koşarken yeniden çizdiğinizi varsayalım. Her karede şapkasını, bıyığını ve "M"yi yeniden çizebilir ya da animasyonu çalıştırmadan önce Mario'yu önceden oluşturabilirsiniz. ön işleme 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);
}

İlerleyen bölümlerde daha ayrıntılı olarak ele alınacak olan requestAnimationFrame kullanımına dikkat edin.

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ş serbest" test senaryosunun kötü performansı. Önceden oluşturma sırasında geçici tuvalinizin çizdiğiniz resmin etrafına tam oturduğundan emin olmak önemlidir. Aksi takdirde, ekran dışı oluşturma işleminin performans kazancı, büyük bir tuvalin diğerine kopyalanmasındaki performans kaybına karşılık gelir (bu, kaynak hedef boyutunun işlevi olarak değişir). Yukarıdaki testteki rahat bir tuval daha küçüktür:

can2.width = 100;
can2.height = 40;

Daha düşük performansa neden olan esnek sürüme kıyasla:

can3.width = 300;
can3.height = 100;

Toplu tuval çağrılarını birlikte toplayın

Çizim pahalı bir işlem olduğundan, çizim durum makinesine uzun bir komut grubu yüklemek ve daha sonra bunların tümünü video arabelleğine almak daha verimlidir.

Örneğin, birden çok çizgi çizerken, tüm çizgilerle tek bir yol oluşturmak ve bunu tek bir çizim çağrısıyla çizmek daha verimli olur. Diğer kelimelerde, ayrı satırlar ç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 çizildiğinde 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 (jsperf) daha iyidir.

Bununla birlikte, Tuval'de bu kuralın önemli bir istisnası vardır: İstenen nesnenin çiziminde yer alan temel öğelerin küçük sınırlayıcı kutuları varsa (örneğin, yatay ve dikey çizgiler) aslında bunları ayrı olarak oluşturmak daha verimli olabilir (jsperf).

Tuval durumu ile ilgili gereksiz değişiklerden kaçının

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. Bununla birlikte, durum makinesinin değiştirilmesi de performans yüküne yol açabilir. Örneğin, bir sahneyi oluşturmak için birden fazla dolgu rengi kullanırsanız tuvale yerleştirmek yerine renge göre oluşturmak daha ucuz olur. İnce şeritli bir desen oluşturmak için, bir şerit oluşturabilir, renkleri değiştirebilir, sonraki şeridi oluşturabilirsiniz:

for (var i = 0; i < STRIPES; i++) {
  context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
  context.fillRect(i * GAP, 0, GAP, 480);
}

İsterseniz tüm tuhaf ş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);
}

Durum makinesinin değiştirilmesi pahalı bir iş olduğundan, aralıklı yaklaşım beklendiği gibi daha yavaştır.

Yeni durumun tamamını değil, yalnızca ekran farklılıklarını oluştur

Beklendiği gibi, ekranda daha az öğe oluşturmak daha fazla oluşturmaktan daha ucuzdur. Yeniden çizimler arasında yalnızca artımlı farklarınız varsa yalnızca farkı çizerek önemli bir performans artışı elde edebilirsiniz. Diğer bir deyişle, çizimden ö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 grafikleriyle ilgili bilginiz varsa bu tekniği, önceden oluşturulan sınırlayıcı kutunun kaydedilip her oluşturma işleminde temizlendiği "bölgeleri yeniden çizme" olarak da bilinir. JavaScript'teki Nintendo emülatör konuşması'nda da gösterildiği gibi bu teknik, piksel tabanlı oluşturma bağlamları için de geçerlidir.

Karmaşık sahneler için birden çok katmanlı tuval 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. Önceden oluşturma bölümünde gösterildiği gibi, ekrandan oluşturma için başka bir tuval kullanmanın yanı sıra, birbirlerinin üzerine yerleştirilmiş tuvaller de kullanabiliriz. Ön plan tuvalinde şeffaflık kullanarak, oluşturma sırasında alfa karakterlerinin birleştirilmesi için GPU'ya güvenebiliriz. Mutlaka konumlandırılmış iki tuval birbiri üzerine gelecek şekilde aşağıdaki gibi oluşturabilirsiniz.

<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 tuvale sahip olmanın avantajı şudur: Ön plan kanvasını çizdiğimizde veya temizlediğimizde arka planı hiçbir zaman değiştirmiyoruz. Oyununuz veya multimedya uygulamanız ön plana ve arka plana bölünebiliyorsa performansı önemli ölçüde artırmak için bunları ayrı tuvallerde oluşturmayı düşünün.

Çoğunlukla insan algısının kusurlu olmasından yararlanarak arka planı, ön plana kıyasla yalnızca bir kez veya daha düşük bir hızda oluşturabilirsiniz (bu işlem kullanıcınızın dikkatinin çoğunu alır). Örneğin, her oluşturma işleminizde ön planı, arka planı ise 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 çok sayıda birleşik kanvas için epey genel olacağını da unutmayın.

shadowBlur'dan kaçın

Diğer birçok grafik ortamında olduğu gibi, HTML5 tuval, geliştiricilerin temel öğeleri 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);

Tuvali temizlemenin çeşitli yollarını öğrenme

HTML5 tuval bir anlık mod çizim paradigması olduğundan, sahnenin her karede açıkça yeniden çizilmesi gerekir. Bu nedenle, tuvalin temizlenmesi HTML5 kanvas uygulama ve oyunları için temelde önemli bir işlemdir. Tuval durumu değişikliklerini önleme bölümünde belirtildiği gibi, tuvalin tamamının temizlenmesi genellikle istenmez ancak bunu yapmak zorundaysanız iki seçenek vardır: context.clearRect(0, 0, width, height) çağrısı yapmak veya bunu yapmak için tuvale özel saldırı kullanmak: canvas.width = canvas.width;.Yazıldığı sırada clearRect genellikle genişlik sıfırlama sürümünden daha iyi performans gösterir, ancak bazı durumlarda canvas.width saldırısının sıfırlanması Chrome 14'te önemli ölçüde daha hızlıdır

Bu ipucu büyük ölçüde temel kanvas uygulamasına bağlı olduğu ve çok fazla değişebileceği için dikkatli olun. Daha fazla bilgi için Simon Sarris'in zemini temizlemeyle ilgili makalesine göz atın.

Kayan nokta koordinatlarından kaçınma

HTML5 kanvas, alt piksel oluşturmayı destekler ve bunu kapatmanın bir yolu yoktur. Tam sayı olmayan koordinatlarla çizim yaparsanız çizgileri düzgün hale getirmek için otomatik olarak kenar yumuşatma kullanır. Seb Lee-Delisle'in bu alt piksel tuval performansı makalesinden alınan görsel efekt:

Alt piksel

İstediğiniz efekt yumuşatılmış imge 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 nokta koordinatlarınızı tam sayılara dönüştürmek için birkaç akıllı teknik kullanabilirsiniz. En iyi performans gösteren teknikler, hedef sayının bir yarısını eklemeyi ve ardından kesirli kısmı elemek için sonuç üzerinde bit tabanlı işlemler 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ı GPU ile hızlandırıldıktan sonra bu tür optimizasyonun artık önemli olmadığını ve tam sayı olmayan koordinatların hızla oluşturulabileceğini unutmayın.

Animasyonlarınızı requestAnimationFrame ile optimize edin

Nispeten yeni requestAnimationFrame API'si, tarayıcıda etkileşimli uygulamalar uygulamak için önerilen yöntemdir. Tarayıcıya oluşturma işlemini belirli bir sabit hızda gerçekleştirme komutu vermek yerine, kibarca tarayıcıdan oluşturma rutininizi çağırmasını ve tarayıcı kullanılabilir olduğunda çağrılmasını istersiniz. İyi bir yan etki olarak, sayfa ön planda değilse tarayıcının oluşturulmayacak kadar akıllı olduğunu gösterir. requestAnimationFrame geri çağırma, 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();

Bu requestAnimationFrame kullanımının tuval için yanı sıra WebGL gibi diğer oluşturma teknolojileri için de geçerli olduğunu unutmayın. Bu yazı yazıldığında bu API yalnızca Chrome, Safari ve Firefox'ta kullanılabildiğinden bu dolguyu kullanmalısınız.

Mobil tuval uygulamalarının çoğu yavaş

Biraz mobil cihazlar hakkında konuşalım. Maalesef belirtilen tarihte, yalnızca Safari 5.1 çalıştıran iOS 5.0 beta sürümü GPU hızlandırmalı mobil tuval uygulamasına sahiptir. GPU hızlandırması olmadan mobil tarayıcılar genellikle tuval tabanlı modern uygulamalar için yeterince güçlü CPU'lara sahip olmaz. Yukarıda açıklanan bazı JSPerf testleri, mobil cihazlarda masaüstüne kıyasla daha kötü performans göstermektedir. Bu da başarıyla çalışmasını bekleyebileceğiniz cihazlar arası uygulama türlerini büyük ölçüde kısıtlar.

Sonuç

Özetlemek gerekirse bu makalede, yüksek performans gösteren HTML5 kanvas tabanlı projeler geliştirmenize yardımcı olacak bir dizi faydalı optimizasyon tekniğini ele aldık. Artık burada yeni bir şeyler öğrendiğinize göre, ilerleyip mükemmel kreasyonlarınızı optimize edebilirsiniz. Şu anda optimize edecek bir oyununuz veya uygulamanız yoksa, ilham almak için Chrome Denemeleri ve Creative JS'ye göz atabilirsiniz.

Referanslar