HTML5 कैनवस की परफ़ॉर्मेंस को बेहतर बनाना

परिचय

HTML5 कैनवस, Apple के एक्सपेरिमेंट के तौर पर शुरू हुआ था. यह वेब पर 2D इमीडिएट मोड ग्राफ़िक्स के लिए, सबसे ज़्यादा इस्तेमाल किया जाने वाला स्टैंडर्ड है. अब कई डेवलपर, कई तरह के मल्टीमीडिया प्रोजेक्ट, विज़ुअलाइज़ेशन, और गेम के लिए इस पर भरोसा करते हैं. हालांकि, हमारे बनाए जाने वाले ऐप्लिकेशन जितने जटिल होते हैं, डेवलपर को परफ़ॉर्मेंस से जुड़ी समस्याएं उतनी ही ज़्यादा आती हैं. कैनवस की परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के बारे में बहुत सारी जानकारी अलग-अलग जगहों पर उपलब्ध है. इस लेख का मकसद, डेवलपर के लिए इस जानकारी को एक ऐसे संसाधन में इकट्ठा करना है जिसे आसानी से समझा जा सके. इस लेख में, सभी कंप्यूटर ग्राफ़िक्स एनवायरमेंट पर लागू होने वाले बुनियादी ऑप्टिमाइज़ेशन के साथ-साथ कैनवस से जुड़ी तकनीकें शामिल हैं. कैनवस के लागू होने के तरीके में सुधार होने पर, इन तकनीकों में बदलाव हो सकता है. खास तौर पर, जब ब्राउज़र के वेंडर कैनवस जीपीयू ऐक्सेलरेशन को लागू करेंगे, तो परफ़ॉर्मेंस को बेहतर बनाने के लिए बताई गई कुछ तकनीकों का असर कम हो जाएगा. जहां ज़रूरी होगा वहां इस पर ध्यान दिया जाएगा. ध्यान दें कि इस लेख में, HTML5 कैनवस के इस्तेमाल के बारे में नहीं बताया गया है. इसके लिए, HTML5Rocks पर कैनवस से जुड़े ये लेख पढ़ें. इसके अलावा, 'एचटीएमएल5 में दिलचस्पी रखें' साइट पर मौजूद यह चैप्टर या MDN Canvas ट्यूटोरियल देखें.

परफ़ॉर्मेंस की जांच

HTML5 कैनवस की तेज़ी से बदलती दुनिया को ध्यान में रखते हुए, JSPerf (jsperf.com) टेस्ट की मदद से यह पुष्टि की जाती है कि सुझाया गया हर ऑप्टिमाइज़ेशन अब भी काम करता है. JSPerf एक वेब ऐप्लिकेशन है. इसकी मदद से, डेवलपर JavaScript की परफ़ॉर्मेंस की जांच करने वाले टेस्ट लिख सकते हैं. हर टेस्ट, उस नतीजे पर फ़ोकस करता है जिसे आपको हासिल करना है. उदाहरण के लिए, कैनवस को खाली करना. साथ ही, इसमें एक ही नतीजा पाने के लिए कई तरीके शामिल होते हैं. JSPerf, कम समयावधि में हर तरीके को ज़्यादा से ज़्यादा बार चलाता है. साथ ही, हर सेकंड में आइटरेशन की आंकड़ों के हिसाब से अहम संख्या देता है. ज़्यादा स्कोर हमेशा बेहतर होते हैं! JSPerf पर परफ़ॉर्मेंस टेस्ट पेज पर आने वाले लोग, अपने ब्राउज़र पर टेस्ट चला सकते हैं. साथ ही, JSPerf को Browserscope (browserscope.org) पर, नॉर्मलाइज़ किए गए टेस्ट के नतीजे सेव करने की अनुमति दे सकते हैं. इस लेख में बताई गई ऑप्टिमाइज़ेशन तकनीकों का बैकअप, JSPerf के नतीजे से लिया गया है. इसलिए, इस लेख पर वापस जाकर, यह अप-टू-डेट जानकारी देखी जा सकती है कि यह तकनीक अब भी लागू है या नहीं. मैंने एक छोटा हेल्पर ऐप्लिकेशन लिखा है, जो इन नतीजों को ग्राफ़ के तौर पर रेंडर करता है. इन ग्राफ़ को इस लेख में एम्बेड किया गया है.

इस लेख में परफ़ॉर्मेंस के सभी नतीजे, ब्राउज़र के वर्शन के हिसाब से दिए गए हैं. यह एक समस्या है, क्योंकि हमें यह नहीं पता कि ब्राउज़र किस ऑपरेटिंग सिस्टम पर चल रहा था. इससे भी ज़्यादा अहम बात यह है कि परफ़ॉर्मेंस की जांच के दौरान, HTML5 कैनवस को हार्डवेयर से तेज़ किया गया था या नहीं. पता बार में about:gpu पर जाकर, यह पता लगाया जा सकता है कि Chrome के HTML5 कैनवस में हार्डवेयर से तेज़ी लाने की सुविधा चालू है या नहीं.

ऑफ़-स्क्रीन कैनवस पर पहले से रेंडर करना

अगर गेम लिखते समय, स्क्रीन पर कई फ़्रेम में मिलते-जुलते प्राइमिटिव फिर से ड्रॉ किए जा रहे हैं, तो सीन के बड़े हिस्सों को पहले से रेंडर करके, परफ़ॉर्मेंस में काफ़ी बढ़ोतरी की जा सकती है. पहले से रेंडर करने का मतलब है कि किसी अलग ऑफ़-स्क्रीन कैनवस (या कैनवस) का इस्तेमाल करके, कुछ समय के लिए दिखने वाली इमेज को रेंडर करना. इसके बाद, ऑफ़-स्क्रीन कैनवस को फिर से दिखने वाले कैनवस पर रेंडर करना. उदाहरण के लिए, मान लें कि आपको हर सेकंड 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;

एक साथ कई Canvas कॉल करना

ड्रॉइंग एक महंगा ऑपरेशन है. इसलिए, ड्रॉइंग स्टेट मशीन को निर्देशों के लंबे सेट के साथ लोड करना ज़्यादा बेहतर होता है. इसके बाद, उन्हें वीडियो बफ़र में डाल दिया जाता है.

उदाहरण के लिए, एक से ज़्यादा लाइनें खींचते समय, उन सभी लाइनों के साथ एक पाथ बनाना और उसे एक ड्रॉ कॉल के साथ खींचना ज़्यादा बेहतर होता है. दूसरे शब्दों में, अलग-अलग लाइनें खींचने के बजाय:

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

हालांकि, ध्यान दें कि कैनवस के लिए, इस नियम में एक अहम अपवाद है: अगर पसंद के ऑब्जेक्ट को ड्रॉ करने में शामिल प्राइमिटिव के छोटे बाउंडिंग बॉक्स (उदाहरण के लिए, हॉरिज़ॉन्टल और वर्टिकल लाइन) हैं, तो उन्हें अलग से रेंडर करना ज़्यादा असरदार हो सकता है (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);
}

जैसा कि उम्मीद थी, इंटरलेस वाला तरीका धीमा है, क्योंकि स्टेट मशीन बदलना महंगा है.

सिर्फ़ स्क्रीन के अंतर को रेंडर करें, न कि पूरी नई स्थिति को

जैसा कि उम्मीद की जा सकती है, स्क्रीन पर कम रेंडर करने की लागत, ज़्यादा रेंडर करने की लागत से कम होती है. अगर रीड्रॉ के बीच सिर्फ़ बढ़ोतरी वाला अंतर है, तो सिर्फ़ अंतर को ड्रॉ करके, परफ़ॉर्मेंस में काफ़ी बढ़ोतरी की जा सकती है. दूसरे शब्दों में, ड्रॉ करने से पहले पूरी स्क्रीन को मिटाने के बजाय:

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

खींचे गए बाउंडिंग बॉक्स पर नज़र रखें और सिर्फ़ उसे मिटाएं.

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

अगर आपको कंप्यूटर ग्राफ़िक्स के बारे में पता है, तो हो सकता है कि आपको यह तकनीक “रीडॉन क्षेत्र” के तौर पर भी पता हो. इसमें, पहले रेंडर किए गए बाउंडिंग बॉक्स को सेव किया जाता है और फिर हर रेंडरिंग पर उसे हटा दिया जाता है. यह तकनीक, पिक्सल पर आधारित रेंडरिंग कॉन्टेक्स्ट पर भी लागू होती है. इस बारे में, JavaScript के Nintendo एमुलेटर टॉक में बताया गया है.

जटिल सीन के लिए, कई लेयर वाले कैनवस का इस्तेमाल करना

जैसा कि पहले बताया गया है, बड़ी इमेज बनाना महंगा होता है. इसलिए, अगर हो सके, तो इससे बचें. ऑफ़ स्क्रीन रेंडरिंग के लिए, किसी दूसरे कैनवस का इस्तेमाल करने के अलावा, हम एक-दूसरे के ऊपर लेयर वाले कैनवस का भी इस्तेमाल कर सकते हैं. इस बारे में, प्री-रेंडरिंग सेक्शन में बताया गया है. फ़ोरग्राउंड कैनवस में ट्रांसपेरेंसी का इस्तेमाल करके, रेंडर के समय अल्फा को एक साथ कंपोज करने के लिए, जीपीयू का इस्तेमाल किया जा सकता है. इसे इस तरह सेट अप किया जा सकता है. इसमें एक के ऊपर एक, दो कैनवस होते हैं.

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

यहां सिर्फ़ एक कैनवस होने का फ़ायदा यह है कि जब फ़ोरग्राउंड कैनवस को ड्रॉ किया जाता है या उसे मिटाया जाता है, तो बैकग्राउंड में कभी भी बदलाव नहीं होता. अगर आपके गेम या मल्टीमीडिया ऐप्लिकेशन को फ़ोरग्राउंड और बैकग्राउंड में बांटा जा सकता है, तो परफ़ॉर्मेंस को बेहतर बनाने के लिए, इन्हें अलग-अलग कैनवस पर रेंडर करें.

अक्सर, लोगों की गलत समझ का फ़ायदा उठाया जा सकता है. इसके लिए, बैकग्राउंड को सिर्फ़ एक बार या फ़ोरग्राउंड की तुलना में धीमी स्पीड पर रेंडर किया जा सकता है. फ़ोरग्राउंड पर लोगों का ज़्यादा ध्यान रहता है. उदाहरण के लिए, हर बार रेंडर करते समय फ़ोरग्राउंड को रेंडर किया जा सकता है, लेकिन बैकग्राउंड को सिर्फ़ हर Nवें फ़्रेम में रेंडर किया जा सकता है. यह भी ध्यान रखें कि अगर आपका ऐप्लिकेशन इस तरह के स्ट्रक्चर के साथ बेहतर तरीके से काम करता है, तो यह तरीका किसी भी संख्या के कॉम्पोज़िट कैनवस के लिए अच्छा साबित होता है.

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 कैनवस ऐप्लिकेशन और गेम के लिए बुनियादी तौर पर ज़रूरी है. कैनवस की स्थिति में बदलाव न करें सेक्शन में बताया गया है कि अक्सर पूरे कैनवस को मिटाना ज़रूरी नहीं होता. हालांकि, अगर आपको ऐसा करना ज़रूरी है, तो आपके पास दो विकल्प हैं: context.clearRect(0, 0, width, height) को कॉल करना या कैनवस के हिसाब से हैक का इस्तेमाल करना: canvas.width = canvas.width. इस लेख को लिखने के समय, clearRect आम तौर पर चौड़ाई को रीसेट करने वाले वर्शन से बेहतर परफ़ॉर्म करता है. हालांकि, कुछ मामलों में Chrome 14 में canvas.width रीसेट करने वाले हैक का इस्तेमाल करना काफ़ी तेज़ होता है

इस सलाह का इस्तेमाल करते समय सावधानी बरतें, क्योंकि यह कैनवस के लागू होने के तरीके पर काफ़ी निर्भर करता है. साथ ही, इसमें बदलाव भी हो सकता है. ज़्यादा जानकारी के लिए, कैनवस मिटाने के बारे में साइमन सरीस का लेख पढ़ें.

फ़्लोटिंग पॉइंट निर्देशांक का इस्तेमाल न करें

HTML5 कैनवस, सब-पिक्सल रेंडरिंग के साथ काम करता है और इसे बंद नहीं किया जा सकता. अगर पूर्णांक के बजाय किसी दूसरे निर्देशांक के साथ ड्रॉ किया जाता है, तो लाइनों को बेहतर बनाने के लिए, यह ऐंटी-ऐलिऐसिंग का इस्तेमाल अपने-आप करता है. यहां विज़ुअल इफ़ेक्ट दिया गया है. इसे सब-पिक्सल कैनवस की परफ़ॉर्मेंस के बारे में सेब ली-डेलिसल के इस लेख से लिया गया है:

सब-पिक्सल

अगर आपको स्मूद किए गए स्प्राइट का इफ़ेक्ट नहीं चाहिए, तो 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).

ध्यान दें कि कैनवस को जीपीयू की मदद से लागू करने के बाद, इस तरह के ऑप्टिमाइज़ेशन की ज़रूरत नहीं होगी. इससे, नॉन-इंटिजर कोऑर्डिनेट को तेज़ी से रेंडर किया जा सकेगा.

requestAnimationFrame की मदद से अपने ऐनिमेशन ऑप्टिमाइज़ करना

ब्राउज़र में इंटरैक्टिव ऐप्लिकेशन लागू करने के लिए, requestAnimationFrame एपीआई का इस्तेमाल करने का सुझाव दिया जाता है. ब्राउज़र को किसी तय टिक रेट पर रेंडर करने का निर्देश देने के बजाय, ब्राउज़र से अपने रेंडरिंग रूटीन को कॉल करने के लिए कहा जाता है. साथ ही, ब्राउज़र के उपलब्ध होने पर, उसे कॉल किया जाता है. एक अच्छा साइड इफ़ेक्ट यह है कि अगर पेज फ़ोरग्राउंड में नहीं है, तो ब्राउज़र उसे रेंडर नहीं करता. requestAnimationFrame कॉलबैक का मकसद 60 FPS कॉलबैक रेट है, लेकिन इसकी कोई गारंटी नहीं है. इसलिए, आपको यह ट्रैक करना होगा कि आखिरी बार रेंडर करने के बाद कितना समय बीत चुका है. यह कुछ ऐसा दिख सकता है:

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 में उपलब्ध है. इसलिए, आपको इस शिम का इस्तेमाल करना चाहिए.

ज़्यादातर मोबाइल कैनवस को लागू करने में ज़्यादा समय लगता है

आइए, मोबाइल के बारे में बात करते हैं. फ़िलहाल, Safari 5.1 के साथ काम करने वाले iOS 5.0 बीटा वर्शन पर ही, जीपीयू की मदद से तेज़ी से काम करने वाले मोबाइल कैनवस को लागू किया जा सकता है. जीपीयू ऐक्सेलरेशन के बिना, मोबाइल ब्राउज़र में आम तौर पर कैनवस पर काम करने वाले आधुनिक ऐप्लिकेशन के लिए, ज़रूरत के मुताबिक सीपीयू नहीं होते. ऊपर बताए गए JSPerf टेस्ट में से कई, डेस्कटॉप की तुलना में मोबाइल पर काफ़ी खराब परफ़ॉर्म करते हैं. इससे, उन क्रॉस-डिवाइस ऐप्लिकेशन पर काफ़ी पाबंदी लगती है जो आसानी से काम कर सकते हैं.

नतीजा

रीकैप करने के लिए, इस लेख में ऑप्टिमाइज़ेशन की काम की तकनीकों के बारे में बताया गया है. इनकी मदद से, बेहतर परफ़ॉर्म करने वाले HTML5 कैनवस-आधारित प्रोजेक्ट बनाए जा सकते हैं. अब आपने यहां कुछ नया सीख लिया है, तो अपने शानदार क्रिएशन को ऑप्टिमाइज़ करें. इसके अलावा, अगर आपके पास फ़िलहाल ऐसा कोई गेम या ऐप्लिकेशन नहीं है जिसे ऑप्टिमाइज़ करना है, तो Chrome एक्सपेरिमेंट और Creative JS देखें.

रेफ़रंस

  • तुरंत मोड बनाम सेव किया गया मोड.
  • HTML5Rocks के अन्य कैनवस लेख.
  • 'एचटीएमएल5 में डाइव इन करें' का कैनवस सेक्शन.
  • JSPerf की मदद से, डेवलपर JS परफ़ॉर्मेंस टेस्ट बना सकते हैं.
  • Browserscope, ब्राउज़र की परफ़ॉर्मेंस का डेटा सेव करता है.
  • JSPerfView, जो JSPerf टेस्ट को चार्ट के तौर पर रेंडर करता है.
  • कैनवस को मिटाने के बारे में साइमन की ब्लॉग पोस्ट और उनकी किताब, HTML5 Unleashed, जिसमें कैनवस की परफ़ॉर्मेंस के बारे में चैप्टर शामिल हैं.
  • सब-पिक्सल रेंडरिंग की परफ़ॉर्मेंस के बारे में, सेबेस्टियन की ब्लॉग पोस्ट.
  • JS NES एमुलेटर को ऑप्टिमाइज़ करने के बारे में बेन की बातचीत.
  • Chrome DevTools में नया कैनवस प्रोफ़ाइलर.