Meningkatkan performa Kanvas HTML5

Pengantar

Kanvas HTML5, yang dimulai sebagai eksperimen dari Apple, adalah standar yang paling banyak didukung untuk grafis mode langsung 2D di web. Banyak developer kini mengandalkannya untuk berbagai project multimedia, visualisasi, dan game. Namun, seiring kompleksitas aplikasi yang kita buat meningkat, developer secara tidak sengaja mengalami masalah performa. Ada banyak pendapat yang tidak terkait tentang cara mengoptimalkan performa kanvas. Artikel ini bertujuan untuk menggabungkan beberapa bagian isi ini menjadi referensi yang lebih mudah dipahami bagi developer. Artikel ini mencakup pengoptimalan dasar yang berlaku untuk semua lingkungan grafis komputer serta teknik khusus kanvas yang dapat berubah seiring dengan peningkatan implementasi kanvas. Secara khusus, saat vendor browser menerapkan akselerasi GPU kanvas, beberapa teknik performa yang diuraikan dan dibahas kemungkinan akan menjadi kurang berdampak. Hal ini akan dicatat jika diperlukan. Perhatikan bahwa artikel ini tidak membahas penggunaan kanvas HTML5. Untuk itu, baca artikel terkait kanvas ini di HTML5Rocks, bab ini di situs Dive into HTML5, atau tutorial MDN Canvas.

Pengujian performa

Untuk mengatasi dunia kanvas HTML5 yang berubah dengan cepat, pengujian JSPerf (jsperf.com) memverifikasi bahwa setiap pengoptimalan yang diusulkan masih berfungsi. JSPerf adalah aplikasi web yang memungkinkan developer menulis pengujian performa JavaScript. Setiap pengujian berfokus pada hasil yang Anda coba capai (misalnya, menghapus kanvas), dan menyertakan beberapa pendekatan yang mencapai hasil yang sama. JSPerf menjalankan setiap pendekatan sebanyak mungkin dalam jangka waktu yang singkat dan memberikan jumlah iterasi per detik yang bermakna secara statistik. Skor yang lebih tinggi selalu lebih baik. Pengunjung halaman pengujian performa JSPerf dapat menjalankan pengujian di browser mereka, dan mengizinkan JSPerf menyimpan hasil pengujian yang dinormalisasi di Browserscope (browserscope.org). Karena teknik pengoptimalan dalam artikel ini didukung oleh hasil JSPerf, Anda dapat kembali untuk melihat informasi terbaru tentang apakah teknik tersebut masih berlaku atau tidak. Saya telah menulis aplikasi bantuan kecil yang merender hasil ini sebagai grafik, yang disematkan di seluruh artikel ini.

Semua hasil performa dalam artikel ini didasarkan pada versi browser. Ini ternyata menjadi batasan, karena kita tidak tahu OS apa yang digunakan browser itu, atau bahkan yang lebih penting, apakah kanvas HTML5 diakselerasi dengan perangkat keras ketika uji kinerja berjalan atau tidak. Anda dapat mengetahui apakah kanvas HTML5 Chrome diakselerasi hardware dengan membuka about:gpu di kolom URL.

Pra-render ke kanvas di luar layar

Jika Anda menggambar ulang primitif serupa ke layar di beberapa frame, seperti yang sering terjadi saat menulis game, Anda dapat memperoleh peningkatan performa yang besar dengan melakukan pra-rendering sebagian besar tampilan. Pra-rendering berarti menggunakan kanvas (atau kanvas) terpisah di luar layar untuk merender gambar sementara, lalu merender kembali kanvas luar layar ke kanvas yang terlihat. Misalnya, Anda menggambar ulang Mario yang berjalan pada 60 frame per detik. Anda dapat menggambar ulang topi, kumis, dan “M”-nya di setiap frame, atau melakukan pra-rendering Mario sebelum menjalankan animasi. tidak ada pra-rendering:

// canvas, context are defined
function render() {
  drawMario(context);
  requestAnimationFrame(render);
}

pra-rendering:

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);
}

Perhatikan penggunaan requestAnimationFrame, yang akan dibahas lebih mendetail di bagian berikutnya.

Teknik ini sangat efektif jika operasi rendering (drawMario dalam contoh di atas) mahal. Contoh yang bagus untuk hal ini adalah rendering teks, yang merupakan operasi yang sangat mahal.

Namun, performa buruk dari kasus pengujian "longgar yang telah dirender sebelumnya". Saat melakukan pra-rendering, penting untuk memastikan kanvas sementara Anda pas di sekitar gambar yang Anda gambar. Jika tidak, peningkatan performa rendering di luar layar akan diimbangi dengan penurunan performa saat menyalin satu kanvas besar ke kanvas lain (yang bervariasi sebagai fungsi dari ukuran target sumber). Kanvas yang pas dalam pengujian di atas lebih kecil:

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

Dibandingkan dengan yang longgar yang menghasilkan performa yang lebih buruk:

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

Mengelompokkan panggilan kanvas

Karena menggambar adalah operasi yang mahal, akan lebih efisien untuk memuat mesin status gambar dengan sekumpulan perintah yang panjang, lalu memintanya untuk membuang semuanya ke buffer video.

Misalnya, saat menggambar beberapa garis, akan lebih efisien untuk membuat satu jalur dengan semua garis di dalamnya dan menggambarnya dengan satu panggilan gambar. Dengan kata lain, alih-alih menggambar garis terpisah:

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();
}

Kita mendapatkan performa yang lebih baik dengan menggambar satu polyline:

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();

Hal ini juga berlaku untuk dunia kanvas HTML5. Misalnya, saat menggambar jalur yang kompleks, sebaiknya masukkan semua titik ke dalam jalur, bukan merender segmen secara terpisah (jsperf).

Namun, perlu diperhatikan bahwa dengan Canvas, ada pengecualian penting untuk aturan ini: jika primitif yang terlibat dalam menggambar objek yang diinginkan memiliki kotak pembatas kecil (misalnya, garis horizontal dan vertikal), mungkin lebih efisien untuk merendernya secara terpisah (jsperf).

Menghindari perubahan status kanvas yang tidak perlu

Elemen kanvas HTML5 diterapkan di atas mesin status yang melacak hal-hal seperti gaya isian dan goresan, serta titik sebelumnya yang membentuk jalur saat ini. Saat mencoba mengoptimalkan performa grafik, Anda mungkin tergoda untuk hanya berfokus pada rendering grafis. Namun, memanipulasi mesin status juga dapat menimbulkan overhead performa. Misalnya, jika Anda menggunakan beberapa warna isi untuk merender tampilan, lebih murah untuk merender menurut warna, bukan menurut penempatan di kanvas. Untuk merender pola garis tipis, Anda dapat merender garis, mengubah warna, merender garis berikutnya, dll.:

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

Atau render semua garis ganjil, lalu semua garis genap:

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);
}

Seperti yang diharapkan, pendekatan yang bertautan lebih lambat karena perubahan mesin status memerlukan banyak biaya.

Hanya merender perbedaan layar, bukan seluruh status baru

Seperti yang diharapkan, merender lebih sedikit di layar lebih murah daripada merender lebih banyak. Jika hanya memiliki perbedaan inkremental antara penggambaran ulang, Anda bisa mendapatkan peningkatan performa yang signifikan hanya dengan menarik selisihnya. Dengan kata lain, daripada menghapus seluruh layar sebelum menggambar:

context.fillRect(0, 0, canvas.width, canvas.height);

Lacak kotak pembatas yang digambar, dan hanya hapus itu.

context.fillRect(last.x, last.y, last.width, last.height);

Jika Anda sudah terbiasa dengan grafik komputer, Anda mungkin juga mengetahui teknik ini sebagai “wilayah gambar ulang”, tempat kotak pembatas yang dirender sebelumnya disimpan, lalu dihapus pada setiap rendering. Teknik ini juga berlaku untuk konteks rendering berbasis piksel, seperti yang diilustrasikan oleh pembahasan emulator Nintendo JavaScript ini.

Menggunakan beberapa kanvas berlapis untuk layar kompleks

Seperti yang disebutkan sebelumnya, menggambar gambar berukuran besar itu mahal dan harus dihindari jika memungkinkan. Selain menggunakan kanvas lain untuk merender di luar layar, seperti yang diilustrasikan di bagian pra-rendering, kita juga dapat menggunakan kanvas yang ditumpuk di atas satu sama lain. Dengan menggunakan transparansi di kanvas latar depan, kita dapat mengandalkan GPU untuk menggabungkan alfa bersama pada waktu render. Anda dapat menyiapkannya sebagai berikut, dengan dua kanvas yang diposisikan secara mutlak, satu di atas yang lain.

<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>

Keuntungannya dibandingkan hanya memiliki satu kanvas di sini adalah saat kita menggambar atau menghapus kanvas latar depan, kita tidak pernah mengubah latar belakang. Jika game atau aplikasi multimedia Anda dapat dibagi menjadi latar depan dan latar belakang, pertimbangkan untuk merendernya di kanvas terpisah untuk mendapatkan peningkatan performa yang signifikan.

Anda sering kali dapat memanfaatkan persepsi manusia yang tidak sempurna dan merender latar belakang hanya sekali atau dengan kecepatan yang lebih lambat dibandingkan dengan latar depan (yang cenderung menempati sebagian besar perhatian pengguna). Misalnya, Anda dapat merender latar depan setiap kali merender, tetapi merender latar belakang hanya setiap frame ke-N. Perhatikan juga bahwa pendekatan ini digeneralisasi dengan baik untuk sejumlah kanvas komposit jika aplikasi Anda berfungsi lebih baik dengan struktur semacam ini.

Menghindari shadowBlur

Seperti banyak lingkungan grafis lainnya, kanvas HTML5 memungkinkan developer memburamkan elemen primitif, tetapi operasi ini bisa sangat tidak efisien:

context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = 'rgba(255, 0, 0, 0.5)';
context.fillRect(20, 20, 150, 100);

Mengetahui berbagai cara untuk menghapus kanvas

Karena kanvas HTML5 adalah paradigma menggambar mode langsung, adegan perlu digambar ulang secara eksplisit pada setiap frame. Oleh karena itu, membersihkan kanvas adalah operasi yang pada dasarnya penting untuk aplikasi dan game kanvas HTML5. Seperti yang disebutkan di bagian Menghindari perubahan status kanvas, menghapus seluruh kanvas sering kali tidak diinginkan, tetapi jika Anda harus melakukannya, ada dua opsi: memanggil context.clearRect(0, 0, width, height) atau menggunakan hack khusus kanvas untuk melakukannya: canvas.width = canvas.width;.Pada saat penulisan, clearRect umumnya mengungguli versi reset lebar, tetapi dalam beberapa kasus, menggunakan hack reset canvas.width jauh lebih cepat di Chrome 14

Berhati-hatilah dengan tips ini, karena sangat bergantung pada implementasi kanvas yang mendasarinya dan sangat dapat berubah. Untuk informasi selengkapnya, lihat artikel Simon Sarris tentang cara mengosongkan kanvas.

Menghindari koordinat floating point

Kanvas HTML5 mendukung rendering sub-piksel, dan tidak ada cara untuk menonaktifkannya. Jika Anda menggambar dengan koordinat yang bukan bilangan bulat, kanvas akan otomatis menggunakan anti-aliasing untuk mencoba menghaluskan garis. Berikut adalah efek visual, yang diambil dari artikel performa kanvas sub-piksel ini oleh Seb Lee-Delisle:

Sub-piksel

Jika sprite yang dihaluskan bukan efek yang Anda cari, akan jauh lebih cepat untuk mengonversi koordinat menjadi bilangan bulat menggunakan Math.floor atau Math.round (jsperf):

Untuk mengonversi koordinat floating point menjadi bilangan bulat, Anda dapat menggunakan beberapa teknik cerdas, yang paling berperforma tinggi melibatkan penambahan satu setengah ke angka target, lalu melakukan operasi bitwise pada hasilnya untuk menghilangkan bagian pecahan.

// 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;

Perincian performa lengkapnya ada di sini (jsperf).

Perhatikan bahwa pengoptimalan semacam ini tidak akan lagi penting setelah penerapan kanvas diakselerasi GPU yang akan dapat merender koordinat non-bilangan bulat dengan cepat.

Mengoptimalkan animasi dengan requestAnimationFrame

requestAnimationFrame API yang relatif baru adalah cara yang direkomendasikan untuk menerapkan aplikasi interaktif di browser. Daripada memerintahkan browser untuk merender pada kecepatan tick tetap tertentu, Anda dengan sopan meminta browser untuk memanggil rutinitas rendering dan dipanggil saat browser tersedia. Sebagai efek samping yang bagus, jika halaman tidak ada di latar depan, browser cukup cerdas untuk tidak merender. Callback requestAnimationFrame menargetkan kecepatan callback 60 FPS, tetapi tidak menjaminnya, jadi Anda harus melacak jumlah waktu yang telah berlalu sejak render terakhir. Tampilannya mungkin terlihat seperti berikut:

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();

Perhatikan bahwa penggunaan requestAnimationFrame ini berlaku untuk kanvas serta teknologi rendering lainnya seperti WebGL. Pada saat penulisan, API ini hanya tersedia di Chrome, Safari, dan Firefox, sehingga Anda harus menggunakan shim ini.

Sebagian besar penerapan kanvas seluler lambat

Mari kita bahas perangkat seluler. Sayangnya, pada saat penulisan, hanya iOS 5.0 beta yang menjalankan Safari 5.1 yang memiliki implementasi kanvas seluler yang diakselerasi GPU. Tanpa akselerasi GPU, browser seluler umumnya tidak memiliki CPU yang cukup canggih untuk aplikasi berbasis kanvas modern. Sejumlah pengujian JSPerf yang dijelaskan di atas memiliki performa yang jauh lebih buruk di perangkat seluler dibandingkan dengan desktop, yang sangat membatasi jenis aplikasi lintas perangkat yang dapat Anda harapkan untuk berhasil dijalankan.

Kesimpulan

Sebagai ringkasan, artikel ini membahas serangkaian teknik pengoptimalan yang berguna dan komprehensif yang akan membantu Anda mengembangkan project berbasis kanvas HTML5 yang berperforma tinggi. Setelah Anda mempelajari sesuatu yang baru di sini, teruslah mengoptimalkan kreasi Anda yang luar biasa. Atau, jika saat ini Anda tidak memiliki game atau aplikasi untuk dioptimalkan, lihat Eksperimen Chrome dan Creative JS untuk mendapatkan inspirasi.

Referensi