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:
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
- Mode Segera vs. mode yang dipertahankan.
- Artikel kanvas HTML5Rocks lainnya.
- Bagian Canvas dalam Mempelajari HTML5.
- JSPerf memungkinkan developer membuat pengujian performa JS.
- Browserscope menyimpan data performa browser.
- JSPerfView, yang merender pengujian JSPerf sebagai diagram.
- Postingan blog Simon tentang cara membersihkan kanvas, dan bukunya, HTML5 Unleashed, yang mencakup beberapa bab tentang performa Canvas.
- Postingan blog Sebastian tentang performa rendering sub-piksel.
- Pembicaraan Ben tentang cara mengoptimalkan emulator JS NES.
- Profiler kanvas baru di Chrome DevTools.