रेंडर करने के लिए डीप-डाइव: BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

Blink, वेब प्लैटफ़ॉर्म को Chromium में लागू करने का तरीका है. इसमें कॉम्पोज़ करने से पहले रेंडरिंग के सभी चरण शामिल होते हैं. यह कंपोज़र कमिट पर खत्म होता है. इस सीरीज़ के पिछले लेख में, Blink रेंडरिंग आर्किटेक्चर के बारे में ज़्यादा पढ़ा जा सकता है.

Blink, WebKit का फ़ॉर्क है. WebKit, KHTML का फ़ॉर्क है, जिसे 1998 में बनाया गया था. इसमें Chromium के कुछ सबसे पुराने (और सबसे अहम) कोड शामिल हैं. साल 2014 तक, यह साफ़ तौर पर दिख रहा था कि यह कोड पुराना हो गया है. उस साल, हमने BlinkNG के बैनर के तहत कई महत्वाकांक्षी प्रोजेक्ट शुरू किए. इनका मकसद, Blink कोड के संगठन और स्ट्रक्चर में लंबे समय से मौजूद कमियों को ठीक करना था. इस लेख में, BlinkNG और इसके कॉम्पोनेंट प्रोजेक्ट के बारे में बताया गया है: हमने इन्हें क्यों बनाया, इनसे क्या हासिल हुआ, इनके डिज़ाइन को तैयार करने के लिए कौनसे दिशा-निर्देश अपनाए गए, और आने वाले समय में इनमें क्या सुधार किए जा सकते हैं.

BlinkNG से पहले और बाद की रेंडरिंग पाइपलाइन.

एनजी से पहले रेंडरिंग

Blink में रेंडरिंग पाइपलाइन को हमेशा सैद्धांतिक तौर पर चरणों (स्टाइल, लेआउट, पेंट वगैरह) में बांटा जाता था. हालांकि, एब्स्ट्रैक्शन बैरियर में गड़बड़ियां थीं. आम तौर पर, रेंडरिंग से जुड़े डेटा में ऐसे ऑब्जेक्ट शामिल होते हैं जो लंबे समय तक मौजूद रहते हैं और जिनमें बदलाव किया जा सकता है. इन ऑब्जेक्ट में कभी भी बदलाव किया जा सकता था और किया भी गया था. साथ ही, रेंडरिंग के अपडेट के बाद, इनका बार-बार इस्तेमाल किया जाता था और इन्हें रीसाइकल किया जाता था. इस तरह के आसान सवालों के जवाब देना मुश्किल था:

  • क्या स्टाइल, लेआउट या पेंट के आउटपुट को अपडेट करने की ज़रूरत है?
  • इन डेटा को "फ़ाइनल" वैल्यू कब मिलेगी?
  • इस डेटा में कब बदलाव किया जा सकता है?
  • इस ऑब्जेक्ट को कब मिटाया जाएगा?

इसके कई उदाहरण हैं. जैसे:

स्टाइल, स्टाइलशीट के आधार पर ComputedStyle जनरेट करेगा; हालांकि, ComputedStyle में बदलाव किया जा सकता था; कुछ मामलों में, पाइपलाइन के बाद के चरणों में इसमें बदलाव किया जाएगा.

स्टाइल, LayoutObject का ट्री जनरेट करेगा. इसके बाद, लेआउट उन ऑब्जेक्ट पर साइज़ और पोज़िशन की जानकारी एनोटेट करेगा. कुछ मामलों में, लेआउट, ट्री स्ट्रक्चर में भी बदलाव करेगा. लेआउट के इनपुट और आउटपुट के बीच कोई साफ़ फ़र्क़ नहीं था.

स्टाइल, ऐक्सेसरी डेटा स्ट्रक्चर जनरेट करेगा, जो कंपोज़िटिंग के कोर्स का पता लगाता है. साथ ही, स्टाइल के बाद हर चरण में, उन डेटा स्ट्रक्चर में बदलाव किया जाता है.

निचले लेवल पर, रेंडरिंग डेटा टाइप में खास तौर पर पेड़ होते हैं. उदाहरण के लिए, डॉक्यूमेंट ऑब्जेक्ट मॉडल (DOM) ट्री, स्टाइल ट्री, लेआउट ट्री, पेंट प्रॉपर्टी ट्री. साथ ही, रेंडरिंग के चरणों को बार-बार ट्री वॉक के तौर पर लागू किया जाता है. आम तौर पर, ट्री वॉक कंटेनेड होना चाहिए: किसी दिए गए ट्री नोड को प्रोसेस करते समय, हमें उस नोड पर रूट किए गए सबसेट के बाहर की कोई जानकारी ऐक्सेस नहीं करनी चाहिए. RenderingNG से पहले ऐसा कभी नहीं होता था. ट्री वॉक, प्रोसेस किए जा रहे नोड के पूर्वजों से अक्सर जानकारी ऐक्सेस करता था. इस वजह से, सिस्टम बहुत नाज़ुक हो गया और उसमें गड़बड़ियां होने लगीं. ट्री वॉक को ट्री की रूट के अलावा किसी और जगह से शुरू करना भी मुमकिन नहीं था.

आखिर में, कोड में रेंडरिंग पाइपलाइन में कई ऑन-रैंप थे: JavaScript से ट्रिगर किए गए फ़ोर्स किए गए लेआउट, दस्तावेज़ लोड होने के दौरान ट्रिगर किए गए कुछ अपडेट, इवेंट टारगेटिंग के लिए तैयार करने के लिए फ़ोर्स किए गए अपडेट, डिसप्ले सिस्टम से अनुरोध किए गए शेड्यूल किए गए अपडेट, और खास एपीआई, जिन्हें सिर्फ़ टेस्ट कोड के लिए दिखाया गया था. रेंडरिंग पाइपलाइन में कुछ रीकर्सिव और रीएंट्रेंट पाथ भी थे. इसका मतलब है कि किसी एक चरण के बीच से दूसरे चरण की शुरुआत पर जाना. इनमें से हर ऑन-रैंप का अपना अलग व्यवहार था. साथ ही, कुछ मामलों में रेंडरिंग का आउटपुट इस बात पर निर्भर करता था कि रेंडरिंग अपडेट को किस तरह ट्रिगर किया गया था.

हमने क्या बदलाव किया है

BlinkNG, कई छोटे और बड़े सब-प्रोजेक्ट से बना है. इन सभी का एक ही लक्ष्य है, पहले बताई गई आर्किटेक्चर से जुड़ी कमियों को दूर करना. इन प्रोजेक्ट में कुछ दिशा-निर्देश शामिल हैं. इनका मकसद रेंडरिंग पाइपलाइन को ज़्यादा असली पाइपलाइन बनाना है:

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

BlinkNG के सब-प्रोजेक्ट की पूरी सूची पढ़ना मुश्किल है. हालांकि, यहां कुछ खास सब-प्रोजेक्ट के बारे में बताया गया है.

दस्तावेज़ का लाइफ़साइकल

DocumentLifecycle क्लास, रेंडरिंग पाइपलाइन की मदद से, हमारी प्रोग्रेस पर नज़र रखती है. इससे हमें बुनियादी जांच करने में मदद मिलती है, जो पहले बताई गई इनवैरिएंट को लागू करती हैं. जैसे:

  • अगर हम ComputedStyle प्रॉपर्टी में बदलाव कर रहे हैं, तो दस्तावेज़ का लाइफ़साइकल kInStyleRecalc होना चाहिए.
  • अगर DocumentLifecycle की स्थिति kStyleClean या उसके बाद की है, तो अटैच किए गए किसी भी नोड के लिए NeedsStyleRecalc() को गलत दिखाना चाहिए.
  • पेंट लाइफ़साइकल फ़ेज़ में प्रवेश करते समय, लाइफ़साइकल की स्थिति kPrePaintClean होनी चाहिए.

BlinkNG को लागू करने के दौरान, हमने इन इनवैरिएंट का उल्लंघन करने वाले कोड पाथ को व्यवस्थित तरीके से हटा दिया. साथ ही, पूरे कोड में कई और एश्योरमेंट जोड़े, ताकि हम पीछे न जाएं.

अगर आपने कभी भी लो-लेवल रेंडरिंग कोड को देखा है, तो हो सकता है कि आप खुद से पूछें, "मैं यहां कैसे पहुंचा?" जैसा कि पहले बताया गया है, रेंडरिंग पाइपलाइन में कई तरह के एंट्री पॉइंट होते हैं. पहले, इसमें बार-बार इस्तेमाल होने वाले और फिर से इस्तेमाल होने वाले कॉल पाथ शामिल थे. साथ ही, ऐसी जगहें भी शामिल थीं जहां हमने शुरुआत से शुरू करने के बजाय, बीच के किसी चरण में पाइपलाइन में प्रवेश किया था. BlinkNG के दौरान, हमने इन कॉल पाथ का विश्लेषण किया और पाया कि इन सभी को दो बुनियादी स्थितियों में कम किया जा सकता है:

  • रेंडरिंग का सारा डेटा अपडेट करना ज़रूरी है. उदाहरण के लिए, डिसप्ले के लिए नए पिक्सल जनरेट करते समय या इवेंट टारगेटिंग के लिए हिट टेस्ट करते समय.
  • हमें किसी खास क्वेरी के लिए अप-टू-डेट वैल्यू की ज़रूरत होती है. इसकी मदद से, रेंडरिंग के पूरे डेटा को अपडेट किए बिना ही जवाब दिया जा सकता है. इसमें ज़्यादातर JavaScript क्वेरी शामिल हैं. उदाहरण के लिए, node.offsetTop.

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

पाइपलाइनिंग स्टाइल, लेआउट, और पहले से पेंट करना

पेंट से पहले रेंडरिंग के चरणों में, ये काम किए जाते हैं:

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

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

BlinkNG से पहले की समस्या का एक और अच्छा उदाहरण, पेंट अमान्य होना है. पहले, पेंट करने से पहले रेंडरिंग के सभी चरणों में पेंट अमान्य कर दिया जाता था. स्टाइल या लेआउट कोड में बदलाव करते समय, यह पता लगाना मुश्किल था कि पेंट अमान्य करने के लॉजिक में क्या बदलाव करने की ज़रूरत है. साथ ही, इसमें आसानी से गड़बड़ी हो सकती थी, जिससे अमान्य करने की प्रक्रिया में कम या ज़्यादा गड़बड़ियां हो सकती थीं. LayoutNG से जुड़ी इस सीरीज़ के लेख में, पेंट को अमान्य करने वाले पुराने सिस्टम की बारीकियों के बारे में ज़्यादा पढ़ा जा सकता है.

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

यहां कुछ ऐसे अहम प्रोजेक्ट दिए गए हैं जिनमें पेंट करने से पहले, रेंडरिंग के चरणों में आर्किटेक्चर से जुड़ी कमियों को दूर किया गया है.

Project Squad: स्टाइल फ़ेज़ को पाइपलाइन करना

इस प्रोजेक्ट में स्टाइल फ़ेज़ में दो मुख्य समस्याओं को हल किया गया, जिनकी वजह से इसे आसानी से पाइपलाइन नहीं किया जा सका:

स्टाइल फ़ेज़ के दो मुख्य आउटपुट होते हैं: ComputedStyle, जिसमें डीओएम ट्री पर सीएसएस कैस्केड एल्गोरिद्म चलाने का नतीजा होता है; और LayoutObjects का ट्री, जो लेआउट फ़ेज़ के लिए ऑपरेशन का क्रम तय करता है. कॉन्सेप्ट के हिसाब से, कैस्केड एल्गोरिदम को लेआउट ट्री जनरेट करने से पहले चलाया जाना चाहिए. हालांकि, पहले इन दोनों ऑपरेशन को एक साथ चलाया जाता था. Project Squad ने इन दोनों को अलग-अलग चरणों में बांटने में सफलता हासिल की.

पहले, स्टाइल को फिर से कैलकुलेट करने के दौरान ComputedStyle को हमेशा उसकी आखिरी वैल्यू नहीं मिलती थी. कुछ मामलों में, ComputedStyle को पाइपलाइन के बाद के चरण के दौरान अपडेट किया जाता था. Project Squad ने इन कोड पाथ को फिर से व्यवस्थित किया, ताकि स्टाइल फ़ेज़ के बाद ComputedStyle में कभी बदलाव न किया जाए.

LayoutNG: लेआउट फ़ेज़ को पाइपलाइन करना

RenderingNG का यह महत्वपूर्ण प्रोजेक्ट, लेआउट रेंडरिंग के फ़ेज़ को पूरी तरह से फिर से लिखा गया था. यह प्रोजेक्ट, RenderingNG का एक अहम हिस्सा है. हम यहां पूरे प्रोजेक्ट के बारे में पूरी जानकारी नहीं देंगे. हालांकि, BlinkNG प्रोजेक्ट के बारे में कुछ अहम बातें बताना चाहेंगे:

  • पहले, स्टाइल फ़ेज़ से बनाए गए LayoutObject का ट्री, लेआउट फ़ेज़ को मिलता था. साथ ही, ट्री को साइज़ और पोज़िशन की जानकारी के साथ एनोटेट किया जाता था. इसलिए, इनपुट और आउटपुट को अलग-अलग नहीं किया जा सका. LayoutNG में फ़्रैगमेंट ट्री की सुविधा जोड़ी गई है. यह लेआउट का मुख्य और रीड-ओनली आउटपुट होता है. साथ ही, यह रेंडरिंग के बाद के चरणों के लिए मुख्य इनपुट के तौर पर काम करता है.
  • LayoutNG ने लेआउट में containment प्रॉपर्टी को जोड़ा है: किसी दिए गए LayoutObject के साइज़ और पोज़िशन का हिसाब लगाते समय, अब हम उस ऑब्जेक्ट पर रूट किए गए सबट्री से बाहर नहीं देखते. किसी ऑब्जेक्ट के लेआउट को अपडेट करने के लिए ज़रूरी सभी जानकारी पहले से ही कैलकुलेट की जाती है और एल्गोरिदम को सिर्फ़ पढ़ने के लिए इनपुट के तौर पर दी जाती है.
  • पहले, ऐसे कुछ मामले थे जहां लेआउट एल्गोरिदम ठीक से काम नहीं करता था: एल्गोरिदम का नतीजा, लेआउट के सबसे हाल के अपडेट पर निर्भर करता था. LayoutNG ने उन मामलों को खत्म कर दिया है.

प्री-पेंट फ़ेज़

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

  • पेंट इनवैलिडेशन जारी करना: जब हमारे पास अधूरी जानकारी होती है, तो लेआउट के दौरान पेंट इनवैलिडेशन सही तरीके से करना बहुत मुश्किल होता है. इसे सही तरीके से लागू करना काफ़ी आसान है. साथ ही, अगर इसे दो अलग-अलग प्रोसेस में बांटा जाता है, तो यह काफ़ी असरदार हो सकता है: स्टाइल और लेआउट के दौरान, कॉन्टेंट को "शायद पेंट को अमान्य करने की ज़रूरत है" के तौर पर, एक आसान बूलियन फ़्लैग के साथ मार्क किया जा सकता है. पेड़ के आकार को पहले से तय करने के दौरान, हम इन फ़्लैग की जांच करते हैं और ज़रूरत के हिसाब से अमान्य होने की सूचना देते हैं.
  • पेंट प्रॉपर्टी ट्री जनरेट करना: इस प्रोसेस के बारे में आगे ज़्यादा जानकारी दी गई है.
  • पेंट के लिए पिक्सल-स्नैप की गई जगहों का हिसाब लगाना और उन्हें रिकॉर्ड करना: रिकॉर्ड किए गए नतीजों का इस्तेमाल, पेंट फ़ेज़ के साथ-साथ, किसी भी डाउनस्ट्रीम कोड के लिए किया जा सकता है. इसके लिए, अतिरिक्त हिसाब लगाने की ज़रूरत नहीं होती.

प्रॉपर्टी ट्री: एक जैसी जियोमेट्री

RenderingNG में प्रॉपर्टी ट्री की सुविधा को शुरू में ही शामिल किया गया था, ताकि स्क्रोलिंग की जटिलता से निपटा जा सके. वेब पर, विज़ुअल इफ़ेक्ट के अन्य सभी टाइप की तुलना में, स्क्रोलिंग का स्ट्रक्चर अलग होता है. प्रॉपर्टी ट्री से पहले, Chromium के कंपोजिटर ने कॉम्पोज़ किए गए कॉन्टेंट के ज्यामितीय संबंध को दिखाने के लिए, एक "लेयर" हैरारकी का इस्तेमाल किया था. हालांकि, position:fixed जैसी सुविधाओं की पूरी जटिलताएं सामने आने के बाद, यह जल्द ही अलग हो गया. लेयर की हैरारकी में, लेयर के "स्क्रोल पैरंट" या "क्लिप पैरंट" को दिखाने वाले अतिरिक्त नॉन-लोकल पॉइंटर जुड़ गए. इससे, कोड को समझना बहुत मुश्किल हो गया.

प्रॉपर्टी ट्री ने इस समस्या को ठीक किया है. इसके लिए, कॉन्टेंट के ओवरफ़्लो स्क्रोल और क्लिप के पहलुओं को अन्य सभी विज़ुअल इफ़ेक्ट से अलग दिखाया गया है. इससे वेबसाइटों के विज़ुअल और स्क्रोलिंग स्ट्रक्चर को सही तरीके से मॉडल किया जा सका. इसके बाद, हमें प्रॉपर्टी ट्री के ऊपर एल्गोरिदम लागू करने थे. जैसे, कॉम्पोज़िटेड लेयर का स्क्रीन-स्पेस ट्रांसफ़ॉर्म या यह तय करना कि कौनसी लेयर स्क्रोल हुईं और कौनसी नहीं.

हमें जल्द ही पता चला कि कोड में कई अन्य जगहों पर भी ज्यामितीय सवाल पूछे गए थे. (मुख्य डेटा स्ट्रक्चर पोस्ट में पूरी सूची दी गई है.) इनमें से कई में, कॉम्पोज़र कोड के काम करने के तरीके को डुप्लीकेट तरीके से लागू किया गया था. सभी में गड़बड़ियों का अलग-अलग सबसेट था. साथ ही, इनमें से किसी ने भी वेबसाइट के सही स्ट्रक्चर को सही तरीके से मॉडल नहीं किया था. इसके बाद, समस्या हल करने का तरीका साफ़ हो गया: सभी ज्यामिति एल्गोरिद्म को एक ही जगह पर सेंट्रलाइज़ करें और इसका इस्तेमाल करने के लिए, सभी कोड को फिर से लिखें.

ये सभी एल्गोरिदम, प्रॉपर्टी ट्री पर निर्भर करते हैं. इसलिए, प्रॉपर्टी ट्री, RenderingNG की पूरी पाइपलाइन में इस्तेमाल होने वाला मुख्य डेटा स्ट्रक्चर है. इसलिए, एक ही जगह पर मौजूद ज्यामिति कोड के इस लक्ष्य को हासिल करने के लिए, हमें प्री-पेंट में, पाइपलाइन में बहुत पहले प्रॉपर्टी ट्री का कॉन्सेप्ट पेश करना पड़ा. साथ ही, उन सभी एपीआई में बदलाव करना पड़ा जो अब उन पर निर्भर थे, ताकि वे लागू होने से पहले प्री-पेंट को चला सकें.

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

पेंट करने के बाद कॉम्पोज़िट करना: पेंट और कॉम्पोज़िट को पाइपलाइन करना

लेयर बनाने की प्रोसेस से यह पता चलता है कि कौनसा DOM कॉन्टेंट अपनी कॉम्पोज़िट लेयर में जाता है. यह लेयर, GPU टेक्सचर को दिखाती है. RenderingNG से पहले, लेयराइज़ेशन, पेंट करने के बाद नहीं, बल्कि पहले होता था. मौजूदा पाइपलाइन के बारे में यहां देखें–क्रम में हुए बदलाव पर ध्यान दें. हम पहले यह तय करेंगे कि डीओएम के कौनसे हिस्से, कौनसी कंपोजिट लेयर में शामिल किए गए हैं. इसके बाद ही, उन टेक्सचर के लिए डिसप्ले सूचियां बनाई जाएंगी. स्वाभाविक रूप से, ये फ़ैसले कई बातों पर निर्भर करते थे. जैसे, कौनसे डीओएम एलिमेंट ऐनिमेट या स्क्रोल कर रहे थे या जिनमें 3D ट्रांसफ़ॉर्मेशन थे. साथ ही, यह भी कि किन एलिमेंट को किन पर पेंट किया गया था.

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

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

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

हमारा प्लान था कि समय के साथ, सभी कॉल साइट DisableCompositingQueryAssert ऑब्जेक्ट हटा दिए जाएं. इसके बाद, कोड को सुरक्षित और सही बताया जाए. हालांकि, हमें पता चला है कि पेंट करने से पहले लेयर बनाने पर, कई कॉल को हटाना असंभव था. (हम इसे हाल ही में हटा पाए!) कॉम्पोज़िट After Paint प्रोजेक्ट के लिए, यह पहली वजह थी. हमें पता चला है कि किसी ऑपरेशन के लिए, पाइपलाइन का कोई फ़ेज़ तय होने के बावजूद, अगर वह पाइपलाइन में गलत जगह पर है, तो आपको समस्याएं आ सकती हैं.

कॉम्पोज़िट आफ़्टर पेंट प्रोजेक्ट की दूसरी वजह, कॉम्पोज़िट करने से जुड़ी बुक की गड़बड़ी थी. इस गड़बड़ी को इस तरह से भी बताया जा सकता है कि डीओएम एलिमेंट, वेब पेज के कॉन्टेंट के लिए, लेयर बनाने की बेहतर या पूरी स्कीम का 1:1 प्रतिनिधित्व नहीं करते. साथ ही, कॉम्पोज़िटिंग, पेंट करने से पहले की जाती थी. इसलिए, यह ज़्यादातर डीओएम एलिमेंट पर निर्भर करती थी, न कि डिसप्ले लिस्ट या प्रॉपर्टी ट्री पर. प्रॉपर्टी ट्री की सुविधा लॉन्च करने की वजह भी यही थी. प्रॉपर्टी ट्री की तरह ही, अगर सही पाइपलाइन फ़ेज़ का पता लगाया जाता है, उसे सही समय पर चलाया जाता है, और उसे सही मुख्य डेटा स्ट्रक्चर दिए जाते हैं, तो समस्या का समाधान सीधे तौर पर मिल जाता है. प्रॉपर्टी ट्री की तरह ही, यह पक्का करने का एक अच्छा मौका था कि पेंट फ़ेज़ पूरा होने के बाद, पाइपलाइन के बाकी सभी चरणों में इसका आउटपुट नहीं बदलेगा.

फ़ायदे

जैसा कि आपने देखा, अच्छी तरह से तय की गई रेंडरिंग पाइपलाइन से लंबे समय तक काफ़ी फ़ायदे मिलते हैं. इनके अलावा, और भी कई चीज़ें हैं:

  • बेहतर भरोसेमंदता: यह बहुत आसान है. साफ़ तौर पर बताए गए और समझने लायक इंटरफ़ेस वाले कोड को समझना, लिखना, और टेस्ट करना आसान होता है. इससे यह ज़्यादा भरोसेमंद बन जाता है. इससे कोड ज़्यादा सुरक्षित और स्थिर बनता है. साथ ही, क्रैश होने की संभावना कम हो जाती है और इस्तेमाल के बाद काम न करने वाले बग भी कम हो जाते हैं.
  • बड़ी टेस्ट कवरेज: BlinkNG के दौरान, हमने अपने सुइट में कई नई टेस्ट जोड़ी हैं. इसमें यूनिट टेस्ट शामिल हैं, जो इंटरनल की पुष्टि करते हैं. साथ ही, रिग्रेशन टेस्ट भी शामिल हैं, जो हमें उन पुराने गड़बड़ियों को फिर से शुरू होने से रोकते हैं जिन्हें हमने ठीक कर दिया है. इनके अलावा, सार्वजनिक और सभी के साथ मिलकर बनाए गए वेब प्लैटफ़ॉर्म टेस्ट सुइट में भी कई चीज़ें जोड़ी गई हैं. सभी ब्राउज़र, वेब स्टैंडर्ड के मुताबिक होने की जांच करने के लिए इसका इस्तेमाल करते हैं.
  • इसे आसानी से बड़ा किया जा सकता है: अगर किसी सिस्टम को अलग-अलग कॉम्पोनेंट में बांटा गया है, तो मौजूदा कॉम्पोनेंट को बेहतर बनाने के लिए, दूसरे कॉम्पोनेंट को पूरी तरह समझने की ज़रूरत नहीं होती. इससे, सभी लोग आसानी से रेंडरिंग कोड में वैल्यू जोड़ सकते हैं. इसके लिए, उन्हें विशेषज्ञ होने की ज़रूरत नहीं है. साथ ही, इससे पूरे सिस्टम के व्यवहार के बारे में आसानी से जानकारी मिलती है.
  • परफ़ॉर्मेंस: स्पैगेटी कोड में लिखे गए एल्गोरिदम को ऑप्टिमाइज़ करना काफ़ी मुश्किल है. हालांकि, इस तरह की पाइपलाइन के बिना यूनिवर्सल थ्रेड वाले स्क्रोलिंग और ऐनिमेशन या साइट अलग करने के लिए प्रोसेस और थ्रेड जैसी बड़ी चीज़ें हासिल करना लगभग असंभव है. पैरलल प्रोसेसिंग की मदद से, परफ़ॉर्मेंस को काफ़ी बेहतर बनाया जा सकता है. हालांकि, यह प्रोसेस काफ़ी मुश्किल है.
  • आउटपुट और कंटेनमेंट: BlinkNG की मदद से कई नई सुविधाएं उपलब्ध कराई गई हैं. इनकी मदद से, पाइपलाइन को नए और बेहतर तरीके से इस्तेमाल किया जा सकता है. उदाहरण के लिए, अगर हमें बजट खत्म होने तक ही रेंडरिंग पाइपलाइन चलानी है, तो क्या होगा? क्या उन सबट्री के लिए रेंडरिंग को छोड़ा जा सकता है जो फ़िलहाल उपयोगकर्ता के लिए काम की नहीं हैं? content-visibility सीएसएस प्रॉपर्टी, इसी काम के लिए है. किसी कॉम्पोनेंट की स्टाइल को उसके लेआउट के हिसाब से बनाने का क्या मतलब है? इन्हें कंटेनर क्वेरी कहा जाता है.

केस स्टडी: कंटेनर क्वेरी

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

कंटेनर क्वेरी की मदद से, किसी एलिमेंट पर लागू होने वाली स्टाइल, किसी पैरंट एलिमेंट के लेआउट साइज़ पर निर्भर कर सकती हैं. लेआउट के दौरान, लेआउट किए गए साइज़ का हिसाब लगाया जाता है. इसका मतलब है कि हमें लेआउट के बाद स्टाइल को फिर से कैलकुलेट करना होगा. हालांकि, स्टाइल को लेआउट से पहले फिर से कैलकुलेट किया जाता है! इस समस्या की वजह से, हम BlinkNG से पहले कंटेनर क्वेरी लागू नहीं कर पाए.

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

सिद्धांत रूप से, सर्कुलर डिपेंडेंसी को contain CSS प्रॉपर्टी का इस्तेमाल करके हल किया जा सकता है. इससे, किसी एलिमेंट के बाहर रेंडरिंग करने की सुविधा मिलती है, जो उस एलिमेंट के सबट्री में रेंडरिंग पर निर्भर नहीं करती. इसका मतलब है कि कंटेनर में लागू किए गए नए स्टाइल से, कंटेनर के साइज़ पर असर नहीं पड़ सकता. इसकी वजह यह है कि कंटेनर क्वेरी के लिए कंटेनमेंट की ज़रूरत होती है.

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

खास जानकारी वाली भाषा में, कॉन्टेंट को कंट्रोल करने के बारे में बताना एक बात है और उसे सही तरीके से लागू करना दूसरी बात. याद रखें कि BlinkNG का एक लक्ष्य, ट्री वॉक में कंटेनमेंट प्रिंसिपल को शामिल करना था. ट्री वॉक, रेंडरिंग के मुख्य लॉजिक का हिस्सा होते हैं: किसी सबट्री को ट्रैवर्स करते समय, सबट्री के बाहर की किसी जानकारी की ज़रूरत नहीं होनी चाहिए. रेंडरिंग कोड, कॉन्टेंट को कंट्रोल करने के सिद्धांत का पालन करता है, तो सीएसएस कंटेनमेंट को लागू करना काफ़ी आसान और बेहतर होता है.

आने वाले समय में: मुख्य थ्रेड के बाहर कॉम्पोज़िंग … और इससे भी ज़्यादा!

यहां दिखाई गई रेंडरिंग पाइपलाइन, RenderingNG के मौजूदा वर्शन से थोड़ी बेहतर है. इसमें लेयराइज़ेशन को मुख्य थ्रेड से हटा हुआ दिखाया गया है, जबकि फ़िलहाल यह मुख्य थ्रेड पर ही है. हालांकि, अब यह काम पूरा होने में ज़्यादा समय नहीं लगेगा, क्योंकि पेंट करने के बाद कॉम्पोज़िट करने की सुविधा लॉन्च हो चुकी है और लेयर बनाने की सुविधा भी पेंट करने के बाद उपलब्ध है.

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

अच्छी खबर यह है कि ऐसा ज़रूरी नहीं है! Chromium के आर्किटेक्चर का यह हिस्सा, KHTML के दिनों से है. उस समय, सिंगल-थ्रेड वाला प्रोग्रामिंग मॉडल मुख्य तौर पर इस्तेमाल किया जाता था. जब उपभोक्ता-ग्रेड डिवाइसों में मल्टी-कोर प्रोसेसर आम हो गए, तब Blink (पहले इसे WebKit कहा जाता था) में सिंगल-थ्रेड की सुविधा पूरी तरह से शामिल हो गई. हम रेंडरिंग इंजन में ज़्यादा थ्रेडिंग को शामिल करना चाहते थे, लेकिन पुराने सिस्टम में ऐसा करना मुमकिन नहीं था. Rendering NG का एक मुख्य मकसद, इस समस्या से निजात पाना था. साथ ही, रेंडरिंग के काम को पूरी तरह या कुछ हिस्से में किसी दूसरी थ्रेड (या थ्रेड) पर ले जाना था.

BlinkNG के पूरा होने में अब बस कुछ ही समय बचा है. इसलिए, हम इस क्षेत्र को एक्सप्लोर करना शुरू कर रहे हैं. बिना रुकावट के कमिट करना, रेंडरर के थ्रेडिंग मॉडल को बदलने की पहली कोशिश है. कंपोज़िटर कमिट (या सिर्फ़ कमिट) मुख्य थ्रेड और कंपोज़िटर थ्रेड के बीच सिंक करने का एक चरण है. कमिट करने के दौरान, हम मुख्य थ्रेड पर जनरेट किए गए रेंडरिंग डेटा की कॉपी बनाते हैं. इसका इस्तेमाल, कंपोजिटर थ्रेड पर चल रहे डाउनस्ट्रीम कंपोजिंग कोड में किया जाता है. सिंक होने के दौरान, कॉपी करने वाला कोड कंपोजिटर थ्रेड पर चलता है. इस दौरान, मुख्य थ्रेड का एक्ज़ीक्यूशन रोक दिया जाता है. ऐसा इसलिए किया जाता है, ताकि कॉम्पोज़र थ्रेड के डेटा को कॉपी करते समय, मुख्य थ्रेड अपने रेंडरिंग डेटा में बदलाव न करे.

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

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

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