مقدمة
لوحة HTML5، التي بدأت كتجربة من Apple، هي الإطار الأكثر استخدامًا على نطاق واسع لرسومات الوضع الفوري ثنائية الأبعاد على الويب. ويعتمد العديد من المطوّرين الآن على هذه التقنية لتنفيذ مجموعة كبيرة من مشاريع الوسائط المتعددة والمرئيات والألعاب. ومع ذلك، مع زيادة تعقيد التطبيقات التي ننشئها، يواجه المطوّرون بدون قصد قيودًا على الأداء. هناك الكثير من المعلومات غير المترابطة حول تحسين أداء لوحة الالتقاط. تهدف هذه المقالة إلى تجميع بعض هذه المعلومات في مرجع سهل الاستيعاب للمطوّرين. تتضمّن هذه المقالة تحسينات أساسية تنطبق على جميع بيئات رسومات الكمبيوتر، بالإضافة إلى أساليب خاصة باللوحة يمكن أن تتغيّر مع تحسين عمليات تنفيذ اللوحة. وعلى وجه التحديد، مع تنفيذ مورّدي المتصفّحات لعملية تسريع وحدة معالجة الرسومات للّوحة، من المرجّح أن تصبح بعض تقنيات الأداء المُوضّحة أقل تأثيرًا. سيتم الإشارة إلى ذلك حيثما ينطبق. يُرجى العِلم أنّ هذه المقالة لا تتناول استخدام لوحة HTML5. لهذا الغرض، اطّلِع على المقالات المتعلّقة باللوحة على HTML5Rocks، وهذا الفصل على موقع Dive into HTML5 الإلكتروني أو الدليل التعليمي MDN Canvas.
اختبار الأداء
للتعامل مع التغييرات السريعة في لوحة HTML5، تتحقّق اختبارات JSPerf (jsperf.com) من أنّ كل تحسين مقترَح لا يزال يعمل. JSPerf هو تطبيق ويب يتيح للمطوّرين كتابة اختبارات أداء JavaScript. يركز كل اختبار على نتيجة تحاول تحقيقها (على سبيل المثال، محو اللوحة)، ويضمّ طرقًا متعددة لتحقيق النتيجة نفسها. يُجري JSPerf كل نهج بقدر الإمكان خلال فترة زمنية قصيرة ويمنح عددًا ذا دلالة إحصائية من التكرارات في الثانية. وكلما كانت النتيجة أعلى، كان ذلك أفضل. يمكن للزائرين الذين ينتقلون إلى صفحة اختبار الأداء في JSPerf إجراء الاختبار على المتصفّح، والسماح لخدمة JSPerf بتخزين نتائج الاختبار المعدَّلة على Browserscope (browserscope.org). بما أنّ تقنيات التحسين المذكورة في هذه المقالة مستندة إلى نتيجة JSPerf، يمكنك الرجوع إليها للاطّلاع على معلومات محدّثة حول ما إذا كانت التقنية لا تزال سارية أم لا. لقد كتبتُ تطبيقًا مساعدًا صغيرًا يعرض هذه النتائج في شكل رسوم بيانية تم تضمينها في هذه المقالة.
تستند جميع نتائج الأداء الواردة في هذه المقالة إلى
إصدار المتصفّح. يُعدّ ذلك عائقًا، لأنّنا لا نعرف نظام التشغيل الذي كان المتصفّح يعمل عليه، أو ما هو أكثر أهمية، ما إذا كان قد تم تسريع canvas HTML5 باستخدام الأجهزة عند إجراء اختبار الأداء. يمكنك معرفة
ما إذا كانت لوحة HTML5 في Chrome تستخدم ميزة "تسريع الأجهزة" من خلال الانتقال إلى about:gpu
في شريط العناوين.
التقديم المُسبَق على لوحة خارج الشاشة
إذا كنت تعيد رسم عناصر أساسية مشابهة على الشاشة في عدة لقطات، كما هو الحال غالبًا عند كتابة لعبة، يمكنك تحقيق مكاسب كبيرة في الأداء من خلال التقديم المسبق لأجزاء كبيرة من المشهد. ويعني التقديم المُسبَق استخدام لوحات منفصلة (أو لوحات) خارج الشاشة لعرض الصور المؤقتة، ثم عرض اللوحات خارج الشاشة على الشاشة المرئية. على سبيل المثال، لنفترض أنّك تعيد رسم شخصية "ماريو" وهي تركض بمعدّل 60 لقطة في الثانية. يمكنك إعادة رسم قبّته وشاربه والحرف "M" في كل إطار، أو يمكنك تقديم عرض مسبق لشخصية ماريو قبل تشغيل الصورة المتحركة. بدون معالجة مسبقة:
// canvas, context are defined
function render() {
drawMario(context);
requestAnimationFrame(render);
}
العرض المُسبَق:
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
، والذي تتم مناقشته بالتفصيل
في قسم لاحق.
يكون هذا الأسلوب فعّالًا بشكل خاص عندما تكون عملية التقديم
(drawMario
في المثال أعلاه) باهظة التكلفة. وخير مثال على ذلك هو
عرض النص، وهي عملية مكلفة للغاية.
ومع ذلك، يُظهر الأداء السيئ لحالة الاختبار "ملفات تمّ عرضها مسبقًا بشكل غير محكم". عند المعالجة المسبقة، من المهم التأكّد من أنّ اللوحة المؤقتة تلائم الصورة التي ترسمها بشكلٍ محكم، وإلا سيتأثر الأداء الذي تحقّقه المعالجة المسبقة خارج الشاشة بانخفاض الأداء الناتج عن نسخ لوحة كبيرة إلى لوحة أخرى (يختلف ذلك حسب حجم الهدف المصدر). تكون اللوحة المناسبة في الاختبار أعلاه أصغر حجمًا:
can2.width = 100;
can2.height = 40;
مقارنةً بالطريقة الفضفاضة التي تؤدي إلى أداء أسوأ:
can3.width = 300;
can3.height = 100;
تجميع مكالمات لوحات العرض معًا
بما أنّ عملية الرسم عملية باهظة التكلفة، من الأفضل تحميل آلة حالة الرسم بمجموعة طويلة من الأوامر، ثم تفريغ كلّها في ذاكرة التخزين المؤقت للفيديو.
على سبيل المثال، عند رسم خطوط متعددة، من الأفضل إنشاء مسار واحد يحتوي على جميع الخطوط ورسمه من خلال طلب رسم واحد. بعبارة أخرى، بدلاً من رسم خطوط منفصلة:
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();
}
نحصل على أداء أفضل من خلال رسم خط متعدّد الأضلاع واحد:
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();
ينطبق ذلك أيضًا على عالم لوحة HTML5. عند رسم مسار معقد، على سبيل المثال، من الأفضل وضع جميع النقاط في المسار، بدلاً من عرض الأجزاء بشكل منفصل (jsperf).
تجدر الإشارة إلى أنّ هناك استثناءً مهمًا لهذا القاعدة في Canvas: إذا كانت العناصر الأساسية المُستخدَمة في رسم الجسم المطلوب تحتوي على مربّعات حدود صغيرة (مثل الخطوط الأفقية والرأسية)، قد يكون من الأفضل عرضها بشكل منفصل (jsperf).
تجنَّب تغييرات حالة اللوحة غير الضرورية.
يتم تنفيذ عنصر لوحة HTML5 على آلة حالة تتبع عناصر مثل أنماط الملء والخطوط، بالإضافة إلى النقاط السابقة التي تشكّل المسار الحالي. عند محاولة تحسين أداء الرسومات، من المغري التركيز فقط على عرض الرسومات. ومع ذلك، يمكن أن يؤدي التلاعب بآلة الحالة أيضًا إلى زيادة تكلفة الأداء. على سبيل المثال، إذا كنت تستخدم ألوان ملء متعددة لعرض مشهد، يكون الأداء أرخص عند العرض حسب اللون بدلاً من حسب موضع العنصر على اللوحة. لتطبيق خطوط رفيعة، يمكنك عرض شريط وتغيير الألوان وعرض الشريط التالي وما إلى ذلك:
for (var i = 0; i < STRIPES; i++) {
context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
context.fillRect(i * GAP, 0, GAP, 480);
}
أو يمكنك عرض كل الخطوط الفردية ثم كل الخطوط الزوجية:
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);
}
كما هو متوقّع، يكون الأسلوب المتداخل أبطأ لأنّ تغيير آلة الحالة يكون باهظ التكلفة.
عرض الاختلافات في الشاشة فقط، وليس الحالة الجديدة بالكامل
كما هو متوقّع، فإنّ عرض عدد أقل من العناصر على الشاشة يكون أرخص من عرض عدد أكبر. إذا كانت لديك اختلافات متزايدة فقط بين redraws، يمكنك تحسين الأداء بشكل كبير من خلال رسم اختلاف بسيط. بعبارة أخرى، بدلاً من محو الشاشة بأكملها قبل الرسم:
context.fillRect(0, 0, canvas.width, canvas.height);
تتبَّع مربّع الحدود الذي تم رسمه، ولا تمحو سوى هذا المربّع.
context.fillRect(last.x, last.y, last.width, last.height);
إذا كنت على دراية بالرسومات الحاسوبية، قد تعرف أيضًا هذه الأسلوب باسم "مناطق إعادة الرسم"، حيث يتم حفظ مربّع الحدود الذي تم عرضه سابقًا، ثم يتم محوه في كل عملية عرض. تنطبق هذه التقنية أيضًا على سياقات العرض المستندة إلى البكسل، كما هو موضح في محادثة محاكي Nintendo هذه باستخدام JavaScript.
استخدام لوحات متعددة الطبقات للمشاهد المعقّدة
كما ذكرنا سابقًا، إنّ رسم الصور الكبيرة باهظ التكلفة ويجب تجنُّبه إن أمكن. بالإضافة إلى استخدام لوحة أخرى للعرض خارج الشاشة، كما هو موضّح في قسم "العرض المُسبَق"، يمكننا أيضًا استخدام لوحات رسومات مطبّقة فوق بعضها. باستخدام الشفافية في لوحة العناصر الأمامية، يمكننا الاعتماد على وحدة معالجة الرسومات لدمج قيم شفافية العناصر معًا في وقت العرض. يمكنك إعداد ذلك على النحو التالي، باستخدام لوحتَين موضعتَين بدقة فوق بعضهما.
<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>
تتمثل ميزة استخدام لوحة واحدة فقط في أنّه عند رسم لوحة المقدّمة أو محو محتواها، لا نُعدّل الخلفية أبدًا. إذا كان بإمكانك تقسيم اللعبة أو تطبيق الوسائط المتعددة إلى عنصرَين هما عنصر أمامي وعنصر خلفي، ننصحك بعرضهما على لوحتَي رسم منفصلتَين للحصول على تحسين كبير في الأداء.
يمكنك غالبًا الاستفادة من النقص في إدراك الإنسان وعرض الخلفية مرة واحدة فقط أو بسرعة أبطأ مقارنةً بالمقدّمة (التي يُحتمل أن تجذب معظم انتباه المستخدم). على سبيل المثال، يمكنك عرض المقدّمة في كلّ مرّة من مرّات العرض، ولكن يمكنك عرض الخلفية كلّ إطار بعد عدد معيّن من اللقطات. يُرجى العلم أيضًا أنّ هذا الأسلوب يُطبَّق بشكل عام على أي عدد من لوحات الالتقاط المركبة إذا كان تطبيقك يعمل بشكل أفضل مع هذا النوع من البنية.
تجنَّب استخدام shadowBlur.
مثل العديد من بيئات الرسومات الأخرى، تسمح لوحة HTML5 للمطوّرين ب تمويه العناصر الأساسية، ولكن يمكن أن تكون هذه العملية باهظة التكلفة:
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = 'rgba(255, 0, 0, 0.5)';
context.fillRect(20, 20, 150, 100);
التعرّف على الطرق المختلفة لمحو المحتوى من اللوحة
بما أنّ لوحة HTML5 هي نموذج رسم الوضع الفوري،
يجب إعادة رسم المشهد بشكل صريح في كل إطار. لهذا السبب،
يعدّ محوّ الشاشة عملية مهمة بشكل أساسي لتطبيقات وألعاب HTML5
canvas.
كما هو موضّح في قسم تجنُّب تغييرات حالة اللوحة،
يُنصح في العادة بعدم محو اللوحة بأكملها، ولكن إذا كان عليك
فعل ذلك، لديك خياران: استدعاء context.clearRect(0, 0, width, height)
أو استخدام حيلة خاصة باللوحة لتنفيذ ذلك:
canvas.width = canvas.width
;.في وقت كتابة هذه المقالة، تفوق clearRect
بشكل عام الإصدار الذي يعيد ضبط العرض، ولكن في بعض الحالات، يكون استخدام حيلة إعادة ضبط canvas.width
أسرع بكثير في Chrome 14.
يُرجى توخّي الحذر عند استخدام هذه النصيحة، لأنّها تعتمد بشكل كبير على تنفيذ canvas الأساسي وقابلة للتغيير بشكل كبير. لمزيد من المعلومات، يمكنك الاطّلاع على مقالة "سايمون ساريس" حول محو اللوحة.
تجنُّب استخدام إحداثيات النقطة العائمة
تتيح لوحة HTML5 عرض العناصر بدقة أقل من وحدات البكسل، ولا يمكن إيقاف هذه الميزة. وإذا كنت ترسم باستخدام إحداثيات ليست أعدادًا صحيحة، يتم استخدام ميزة تمويه الحواف تلقائيًا لمحاولة تنعيم الخطوط. في ما يلي التأثير المرئي، مأخوذ من هذه المقالة حول أداء لوحة الألوان الفرعية البكسل من تأليف "سيب لي-ديليسلي":
![وحدة البكسل الفرعية](https://web.developers.google.cn/static/articles/canvas-performance/image/sub-pixel-ebd2e5026bd2.png?hl=ar)
إذا لم يكن المؤثر المُبسَّط هو التأثير الذي تبحث عنه، يمكن أن يكون أسرع بكثير
لتحويل إحداثياتك إلى أعداد صحيحة باستخدام Math.floor
أو
Math.round
(jsperf):
لتحويل إحداثيات النقطة العائمة إلى أعداد صحيحة، يمكنك استخدام عدة أساليب ذكية، وأكثرها فعالية هي إضافة نصف إلى الرقم المستهدَف، ثم إجراء عمليات على البتات في النتيجة لإزالة الجزء الكسري.
// 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;
يمكنك الاطّلاع على تفاصيل الأداء الكاملة هنا (jsperf).
تجدر الإشارة إلى أنّ هذا النوع من التحسين لن يُعدّ مهمًا بعد أن يتم تسريع تنفيذات canvas باستخدام وحدة معالجة الرسومات، ما سيتيح عرض إحداثيات غير صحيحة بسرعة.
تحسين الصور المتحركة باستخدام requestAnimationFrame
إنّ واجهة برمجة التطبيقات requestAnimationFrame
الجديدة نسبيًا هي الطريقة المُقترَحة ل
تنفيذ التطبيقات التفاعلية في المتصفّح. بدلاً من
طلب عرض المتصفّح بمعدّل محدّد ثابت للنقرة، يمكنك
طلب عرض المتصفّح لروتين العرض والاتّصال
به عندما يكون المتصفّح متاحًا. كنتيجة جانبية إيجابية، إذا لم تكن الصفحة
في المقدّمة، يكون المتصفّح ذكيًا بما يكفي لعدم عرضها.
يهدف requestAnimationFrame
callback إلى تحقيق معدل استدعاء يبلغ 60 لقطة في الثانية، ولكنه
لا يضمن ذلك، لذا عليك تتبُّع الوقت الذي انقضى
منذ آخر عملية عرض. يمكن أن يبدو هذا على النحو التالي:
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
هذا ينطبق على مساحة الرسم بالإضافة إلى
تقنيات العرض الأخرى، مثل WebGL.
في وقت كتابة هذه المقالة، لا تتوفّر واجهة برمجة التطبيقات هذه إلا في Chrome وSafari و Firefox، لذا عليك استخدام هذا العنصر الوسيط.
بطء معظم عمليات تنفيذ لوحات الإعلانات على الأجهزة الجوّالة
لنتحدث عن الأجهزة الجوّالة. في وقت كتابة هذه المقالة، لا يتوفّر تنفيذ canvas
المعزّز بوحدة معالجة الرسومات إلا في الإصدار التجريبي من iOS
5.0 الذي يعمل بإصدار Safari 5.1. بدون تسريع وحدة معالجة الرسومات، لا تتضمّن متصفحات الأجهزة الجوّالة عمومًا معالجات مركزية قوية بما يكفي لتشغيل التطبيقات الحديثة المستندة إلى الشاشة الكاملة. يُظهر عدد من اختبارات JSPerf الموضّحة أعلاه أداءً
أسوأ بكثير على الأجهزة الجوّالة مقارنةً بأجهزة الكمبيوتر المكتبي، ما يؤدي إلى محدودية
كبيرة في أنواع التطبيقات المتوافقة مع جميع الأجهزة التي يمكنك توقّع
تشغيلها بنجاح.
الخاتمة
باختصار، تناولت هذه المقالة مجموعة شاملة من تقنيات التحسين المفيدة التي ستساعدك في تطوير مشاريع فعّالة مستندة إلى canvas في HTML5. بعد أن اطّلعت على معلومات جديدة، ننصحك بالبدء في تحسين إبداعاتك الرائعة. وإذا لم يكن لديك حاليًا لعبة أو تطبيقًا لتحسينهما، يمكنك الاطّلاع على تجارب Chrome وCreative JS للحصول على أفكار.
المراجع
- الوضع المباشر في مقابل الوضع المحتفظ به
- مقالات أخرى حول Canvas في HTML5Rocks
- قسم Canvas في "الاطّلاع على HTML5"
- يتيح JSPerf للمطوّرين إنشاء اختبارات أداء JS.
- تخزِّن أداة Browserscope بيانات أداء المتصفّح.
- JSPerfView، الذي يعرض اختبارات JSPerf كرسوم بيانية
- مقالة مدوّنة كتبها "سايمون" حول تنظيف اللوحة، وكتابه HTML5 Unleashed الذي يتضمّن فصولًا عن أداء Canvas
- مقالة سيباستيان في المدونة حول أداء العرض على مستوى البكسل الفرعي
- محادثة بين "بن" وأحد المستخدمين حول تحسين محاكي NES لنظام التشغيل JavaScript
- أداة تحليل أداء مساحة الرسم الجديدة في "أدوات مطوّري البرامج في Chrome"