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, visualisasi, dan game multimedia. Namun, seiring macamnya kompleksitas aplikasi yang kita build, developer secara tidak sengaja mengalami hambatan performa. Ada banyak kebijaksanaan yang tidak saling terhubung tentang cara mengoptimalkan performa kanvas. Artikel ini bertujuan untuk menggabungkan sebagian 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 peningkatan implementasi kanvas. Secara khusus, karena vendor browser menerapkan akselerasi GPU kanvas, beberapa teknik performa yang telah dijelaskan kemungkinan akan menjadi kurang berdampak. Hal ini akan dicatat jika sesuai. Perhatikan bahwa artikel ini tidak membahas penggunaan kanvas HTML5. Untuk itu, lihat artikel terkait kanvas di HTML5Rocks, bab tentang situs Dive into HTML5, atau 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 ingin Anda capai (misalnya, membersihkan kanvas), dan menyertakan beberapa pendekatan yang mencapai hasil yang sama. JSPerf menjalankan setiap pendekatan sebanyak mungkin selama 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 dinormalkan 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 dikunci di versi browser. Hal ini ternyata menjadi batasan, karena kita tidak tahu OS apa yang menjalankan browser, atau bahkan yang lebih penting, apakah kanvas HTML5 diakselerasi dengan hardware saat pengujian performa berjalan atau tidak. Anda dapat mengetahui apakah kanvas HTML5 Chrome mengaktifkan akselerasi hardware dengan membuka about:gpu di kolom URL.

Melakukan pra-render ke kanvas di luar layar

Jika Anda menggambar ulang primitif yang serupa ke layar dalam beberapa frame, seperti yang sering terjadi saat menulis game, Anda dapat memperoleh peningkatan performa yang besar dengan melakukan pra-rendering sebagian besar scene. Pra-rendering berarti menggunakan kanvas (atau kanvas) terpisah di luar layar untuk merender gambar sementara, lalu merender kanvas di luar layar kembali ke kanvas yang terlihat. Misalnya, Anda menggambar ulang Mario yang berjalan pada 60 frame detik. Anda bisa menggambar ulang topi, kumis, dan “M” di setiap frame, atau merender Mario terlebih dahulu 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 dibahas secara lebih mendetail di bagian selanjutnya.

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

Namun, performa buruk dari kasus pengujian “longgar yang dirender sebelumnya”. Saat melakukan pra-rendering, penting untuk memastikan bahwa kanvas sementara Anda pas di sekitar gambar yang Anda gambar. Jika tidak, peningkatan performa rendering di luar layar diseimbangkan dengan hilangnya performa saat menyalin satu kanvas besar ke kanvas lain (yang bervariasi sesuai fungsi ukuran target sumber). Kanvas yang nyaman dalam pengujian di atas memiliki ukuran yang lebih kecil:

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

Dibandingkan dengan ekstensi bebas yang menghasilkan performa lebih buruk:

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

Mengelompokkan panggilan kanvas bersama-sama

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

Misalnya, saat menggambar beberapa garis, akan lebih efisien untuk membuat satu jalur yang berisi semua garis di dalamnya dan menggambarnya dengan satu panggilan gambar. Dengan kata lain, daripada 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 akan 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 tempatkan semua titik ke dalam jalur, daripada merender segmen secara terpisah (jsperf).

Namun, perhatikan 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 pada mesin status yang melacak hal-hal seperti gaya isian dan goresan, serta titik-titik sebelumnya yang membentuk jalur saat ini. Saat mencoba mengoptimalkan performa grafis, Anda mungkin tergoda untuk hanya berfokus pada rendering grafis. Namun, memanipulasi mesin status juga dapat menimbulkan overhead performa. Misalnya, jika Anda menggunakan beberapa warna isian untuk merender tampilan, akan lebih murah untuk merender berdasarkan warna daripada penempatan pada kanvas. Untuk merender pola strip, Anda dapat merender setrip, 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 mengganti mesin status itu mahal.

Hanya render perbedaan layar, bukan status baru

Seperti yang diharapkan, rendering yang lebih sedikit pada layar lebih murah daripada merender lebih banyak. Jika hanya mengalami perbedaan inkremental di antara penggambaran ulang, Anda bisa mendapatkan peningkatan performa yang signifikan hanya dengan menggambarkan perbedaannya. Dengan kata lain, daripada membersihkan seluruh layar sebelum menggambar:

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

Lacak kotak pembatas yang digambar, dan hanya bersihkan.

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

Jika sudah terbiasa dengan grafis komputer, Anda mungkin juga mengenal teknik ini sebagai "menggambar ulang region", 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 Nintendo emulator talk JavaScript ini.

Menggunakan beberapa kanvas berlapis untuk suasana yang 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 dilapiskan di atas satu sama lain. Dengan menggunakan transparansi di kanvas latar depan, kita dapat mengandalkan GPU untuk menggabungkan alfa bersama-sama 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>

Keuntungan hanya memiliki satu kanvas di sini, adalah ketika kita menggambar atau mengosongkan kanvas latar depan, kita tidak pernah mengubah latar belakang. Jika game atau aplikasi multimedia 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 di latar depan (yang kemungkinan memenuhi sebagian besar perhatian pengguna). Misalnya, Anda dapat merender latar depan setiap kali melakukan rendering, tetapi merender latar belakang hanya setiap frame ke-N. Perhatikan juga bahwa pendekatan ini dapat 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 untuk memburamkan primitif, tetapi operasi ini dapat memerlukan banyak resource:

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

Karena kanvas HTML5 adalah paradigma menggambar mode langsung, adegan harus digambar ulang secara eksplisit pada setiap frame. Oleh karena itu, mengosongkan kanvas adalah operasi yang sangat 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 peretasan khusus kanvas untuk melakukannya: canvas.width = canvas.width;.Pada saat penulisan, clearRect umumnya mengungguli versi reset lebar, tetapi dalam beberapa kasus, menggunakan peretasan reset canvas.width jauh lebih cepat di Chrome 14

Berhati-hatilah dengan tips ini, karena tip ini sangat bergantung pada implementasi kanvas yang mendasarinya dan sangat mudah berubah. Untuk mengetahui informasi selengkapnya, baca artikel Simon Sarris tentang membersihkan 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 efek visualnya, yang diambil dari artikel performa kanvas sub-piksel oleh Seb Lee-Delisle:

Subpiksel

Jika sprite yang dihaluskan bukan efek yang Anda cari, mengonversi koordinat menjadi bilangan bulat dapat jauh lebih cepat 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 hasil 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 lengkap tersedia di sini (jsperf).

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

Optimalkan animasi Anda dengan requestAnimationFrame

requestAnimationFrame API yang relatif baru adalah cara yang direkomendasikan untuk mengimplementasikan aplikasi interaktif di browser. Daripada perintah browser untuk merender pada kecepatan tick tetap tertentu, Anda dengan sopan meminta browser untuk memanggil rutinitas rendering dan dipanggil saat browser tersedia. Efek samping yang bagus, jika halaman tidak ada di latar depan, browser cukup cerdas untuk tidak merender. Callback requestAnimationFrame bertujuan untuk kecepatan callback 60 FPS, tetapi tidak menjaminnya, jadi Anda perlu melacak jumlah waktu yang telah berlalu sejak render terakhir. Hal ini dapat 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();

Perlu diperhatikan 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, jadi Anda harus menggunakan shim ini.

Sebagian besar penerapan kanvas seluler lambat

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

Kesimpulan

Sebagai rangkuman, artikel ini membahas serangkaian komprehensif teknik pengoptimalan yang berguna yang akan membantu Anda mengembangkan proyek berbasis kanvas HTML5 yang berperforma tinggi. Setelah mempelajari hal baru di sini, lanjutkan dan optimalkan kreasi Anda yang luar biasa. Atau, jika saat ini Anda belum memiliki game atau aplikasi yang dapat dioptimalkan, lihat Eksperimen Chrome dan JS Materi Iklan untuk mendapatkan inspirasi.

Referensi