इस साइट की सामग्री का अनुवाद कृत्रिम बुद्धिमत्ता (AI) या मशीन अनुवाद तकनीक का उपयोग करके किया गया है, और इसमें त्रुटियाँ हो सकती हैं.

Skip to content

धातु पुनरावलोकन

हमने सफलतापूर्वक मेटल रेंडरिंग बैकएंड को लाखों उपयोगकर्ताओं तक पहुँचाया है, और मैं इसके बारे में थोड़ा लिखना चाहता हूँ। उद्योग में Metal को लेकर अलग-अलग राय हैं - कुछ का दावा है कि अगर Apple ने OpenGL और Vulkan पर अधिक ध्यान दिया होता तो Metal की ज़रूरत नहीं पड़ती, कुछ कहते हैं कि यह अब तक का सबसे आसान ग्राफिक्स API है। कुछ लोग पूछते हैं कि Metal के साथ झंझट क्यों करें, जब आप बस OpenGL या Vulkan कोड लिख सकते हैं, और उसी प्रभाव के लिए MoltenGL या MoltenVK का उपयोग कर सकते हैं? यहाँ इस API पर मेरे विचार हैं।

मेटल क्यों?

जब 2014 में WWDC में Apple ने Metal की घोषणा की, तो मेरी शुरुआती प्रतिक्रिया इसे अनदेखा करने की थी। यह केवल सबसे नए हार्डवेयर पर उपलब्ध था, जो हमारे अधिकांश उपयोगकर्ताओं के पास नहीं था। और यद्यपि Apple ने दावा किया कि Metal ने CPU प्रदर्शन संबंधी समस्याओं को हल किया, हमारे लिए सबसे छोटे बाज़ार के लिए अनुकूलन करने का मतलब था कि सबसे तेज़ उपकरणों और सबसे धीमे उपकरणों के बीच का अंतर और भी बढ़ जाए। उस समय, हम केवल Apple पर OpenGL ES 2 चला रहे थे, और Android पर पोर्ट करना भी शुरू कर रहे थे।

ढाई साल बाद, हमारे उपयोगकर्ताओं के लिए मेटल का बाज़ार हिस्सेदारी इस प्रकार दिखती है:

यह पहले की तुलना में कहीं अधिक आकर्षक है। अभी भी Metal को लागू करने से सबसे पुराने उपकरणों को कोई लाभ नहीं होता, लेकिन iOS पर GL बाजार लगातार सिकुड़ रहा है। इसके अलावा, सबसे पुराने डिवाइसों पर हम जो सामग्री चलाते हैं, वह अक्सर नवीनतम डिवाइसों पर चलने वाली सामग्री से अलग होती है, इसलिए इन डिवाइसों को तेज़ बनाने के लिए कुछ प्रयास करना निश्चित रूप से समझ में आता है। यह देखते हुए कि आपका iOS Metal कोड बहुत कम बदलाव के साथ Mac पर चलेगा, इसलिए भले ही आप मोबाइल-केंद्रित हों, Mac पर भी इसका उपयोग करना समझ में आ सकता है (हम वर्तमान में केवल iOS पर Metal बिल्ड प्रदान करते हैं)।

मुझे लगता है कि बाजार हिस्सेदारी का थोड़ा और विस्तार से विश्लेषण करना सार्थक है। iOS पर, हम iOS 8.3+ के लिए मेटल का समर्थन करते हैं। हालाँकि कुछ उपयोगकर्ता OS संस्करण प्रतिबंधों के कारण मेटल नहीं चला सकते हैं, लेकिन 25% में से अधिकांश जो अभी भी GL चलाते हैं, वे बस पुराने डिवाइस का उपयोग कर रहे हैं जिनमें SGX हार्डवेयर है। उनके पास OpenGL ES 3 की कोई सुविधा भी नहीं है, और हम वहाँ एक कम-स्तरीय रेंडरिंग पथ चलाने से संतुष्ट हैं (हालांकि, हम चाहेंगे कि सभी डिवाइस मेटल पर आ जाएँ। सौभाग्य से, GL/Metal का विभाजन केवल बेहतर होगा)। Mac पर, Metal API नया है और OS की भूमिका काफी महत्वपूर्ण है। Metal का उपयोग करने के लिए आपको OSX 10.11+ का उपयोग करना होगा, और हमारे आधे उपयोगकर्ताओं के पास बस एक पुराना OS है। यह हार्डवेयर से कम और सॉफ्टवेयर से अधिक संबंधित है (हमारे 95% Mac उपयोगकर्ता OpenGL 3.2+ का उपयोग करते हैं)।

तो, बाज़ार हिस्सेदारी को देखते हुए, अभी भी अन्य विकल्प मौजूद हैं जिनमें मेटल पर पोर्ट करना शामिल नहीं है। उनमें से एक है सिर्फ मोल्टनजीएल (MoltenGL) का उपयोग करना, जो हमारे पास पहले से मौजूद ओपनजीएल (OpenGL) कोड का उपयोग करेगा, लेकिन कथित तौर पर तेज़ होगा; दूसरा है वल्कन (Vulkan) पर पोर्ट करना (पीसी पर बेहतर प्रदर्शन पाने के लिए, और अंततः एंड्रॉइड पर); या मोल्टनवीके (MoltenVK) का उपयोग करना। मैंने संक्षेप में मोल्टनजीएल का मूल्यांकन किया है और मैं इसके परिणामों से बहुत उत्साहित नहीं था। हमारे कोड को चलाने में ही काफी मेहनत लगी, और जबकि स्टॉक OpenGL की तुलना में प्रदर्शन थोड़ा बेहतर था, मैं इससे और अधिक की उम्मीद कर रहा था। जहाँ तक MoltenVK की बात है, मुझे लगता है कि एक लो-लेवल API को दूसरे के ऊपर एक परत के रूप में लागू करने का प्रयास करना गलत है। इससे अवश्य ही इम्पीडेंस मिसमैच (impedance mismatch) होगा, जिसके परिणामस्वरूप प्रदर्शन अनुकूल नहीं होगा। हो सकता है कि यह उस हाई-लेवल API से बेहतर हो जिसका आप पहले उपयोग करते थे, लेकिन इसके अधिकतम संभव तेज़ होने की संभावना नहीं है, और कथित तौर पर यही कारण है कि आप शुरू में ही एक लो-लेवल API चुन रहे हैं! एक और महत्वपूर्ण पहलू यह है कि मेटल का कार्यान्वयन वल्कन की तुलना में बहुत सरल है - इस पर बाद में और बात करेंगे - इसलिए एक तरह से मैं वल्कन -> मेटल के बजाय मेटल -> वल्कन रैपर को प्राथमिकता दूँगा।

यह ध्यान देने योग्य भी है कि नवीनतम iPhones पर iOS 10 में, जाहिर तौर पर, कोई GL ड्राइवर नहीं है। GL, Metal के ऊपर लागू किया गया है, जिसका मतलब है कि OpenGL का उपयोग करना वास्तव में आपको केवल थोड़ी सी विकास मेहनत ही बचाता है - इतनी भी नहीं, यह देखते हुए कि OpenGL का "एक बार लिखें, कहीं भी चलाएं" का वादा मोबाइल पर वास्तव में काम नहीं करता है।

पोर्टिंग

मैं कहूँगा कि कुल मिलाकर मेटल पर पोर्ट करना बहुत आसान था। हमारे पास विभिन्न ग्राफिक्स एपीआई के साथ काम करने का बहुत अनुभव है, जिसमें Direct3D 9/11 जैसे उच्च स्तरीय एपीआई से लेकर कंसोल प्लेटफॉर्म पर निम्न स्तरीय एपीआई तक शामिल हैं। यह मेटल जैसे एपीआई का आराम से उपयोग करने का एक अनूठा लाभ देता है जो एक ही समय में काफी हद तक उच्च स्तरीय है, लेकिन साथ ही सीपीयू-जीपीयू सिंक्रनाइज़ेशन जैसे कुछ कार्य ऐप डेवलपर के लिए करने के लिए छोड़ देता है।

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

  • आप हर चरण में अच्छे फीडबैक के साथ, क्रमबद्ध तरीके से कोड विकसित कर सकते हैं। हमारा कोड शुरुआत में सभी CPU-GPU सिंक्रोनाइज़ेशन को अनदेखा कर रहा था, स्टेट सेटअप के कुछ हिस्सों में बहुत ही अनुकूल नहीं था, संसाधनों के लिए अंतर्निहित संदर्भ ट्रैकिंग का उपयोग कर रहा था, और समस्याओं से बचने के लिए कभी भी CPU और GPU को समानांतर रूप से नहीं चला रहा था। फिर ऑप्टिमाइज़ेशन/पॉलिश चरण ने इसे कुछ ऐसा में बदल दिया जिसे हम शिप कर सकते थे, और इस प्रक्रिया में रेंडर करने की क्षमता कभी नहीं खोई।
  • आपके लिए उपकरण उपलब्ध हैं; वे काम करते हैं और वे अच्छी तरह से काम करते हैं। यह उन लोगों के लिए उतना आश्चर्य की बात नहीं है जो Direct3D 11 के आदी हैं, लेकिन यह मोबाइल पर पहली बार था जब मेरे पास एक CPU प्रोफाइलर, एक GPU प्रोफाइलर, एक GPU डिबगर, और एक GPU API वैलिडेशन लेयर थे जो सभी एक साथ अच्छी तरह से काम करते थे, विकास के दौरान अधिकांश समस्याओं को पकड़ते थे और कोड को अनुकूलित करने में मदद करते थे।
  • हालांकि यह एपीआई Direct3D 11 की तुलना में कुछ हद तक निचले स्तर की है, और यह कुछ प्रमुख निम्न-स्तरीय निर्णय डेवलपर पर छोड़ देती है (जैसे कि रेंडर पास कॉन्फ़िगरेशन या सिंक्रनाइज़ेशन), फिर भी यह एक पारंपरिक संसाधन मॉडल का उपयोग करती है जहाँ प्रत्येक संसाधन में कुछ "उपयोग फ़्लैग" होते हैं जिनके साथ इसे बनाया गया है, लेकिन इसके लिए पाइपलाइन बैरियर या लेआउट ट्रांज़िशन की आवश्यकता नहीं होती है, और एक पारंपरिक बाइंडिंग मॉडल जहाँ प्रत्येक शेडर चरण में कई स्लॉट होते हैं जिनमें आप स्वतंत्र रूप से संसाधनों को असाइन कर सकते हैं। ये दोनों परिचित हैं, समझने में आसान हैं, और तेज़ी से काम शुरू करने के लिए बहुत कम कोड की आवश्यकता होती है।

एक और चीज़ जिसने मदद की वह यह है कि हमारा एपीआई इंटरफ़ेस मेटल-जैसे एपीआई के लिए तैयार था। यह बहुत ही संक्षिप्त है, लेकिन यह पर्याप्त विवरण (जैसे रेंडर पास) प्रदर्शित करता है ताकि एक प्रदर्शनक्षम कार्यान्वयन को आसानी से लिखा जा सके। हमारे कार्यान्वयन में किसी भी समय मुझे स्थिति (स्टेट) को सहेजने/बहाल करने (स्टेट सेव/रिस्टोर करने) की आवश्यकता नहीं पड़ी (कई एपीआई इंटरफ़ेस इससे पीड़ित हैं, विशेष रूप से रेंडर टारगेट सेटअप को स्थिति परिवर्तन के रूप में मानने और संसाधन/स्थिति बाइंडिंग के उस माध्यम से बने रहने के कारण) या संसाधन के जीवनकाल/सिंक्रनाइज़ेशन के बारे में जटिल निर्णय लेने की ज़रूरत नहीं पड़ी। रेंडर करने के लिए ज़रूरी कोड का लगभग एकमात्र "जटिल" हिस्सा वह है जो रेंडर पाइपलाइन स्टेट बनाने के लिए ज़रूरी बिट्स को हैश करके स्टेट बनाता है - पाइपलाइन स्टेट ऑब्जेक्ट हमारे API एब्स्ट्रैक्शन का हिस्सा नहीं हैं। वह भी काफी सीधा-सादा और तेज़ है। मैं हमारे API इंटरफ़ेस के बारे में एक अलग पोस्ट में और लिखूँगा।

तो, शेडर्स को कंपाइल करने में एक हफ्ता, एक पॉलिश और ऑप्टिमाइज़्ड इम्प्लीमेंटेशन तैयार करने में दो हफ्ते ((हाँ, ठीक है, और शायद टेस्टिंग के दौरान मिले कुछ बग्स को ठीक करने में एक हफ्ता)) - परिणाम क्या हैं? परिणाम शानदार हैं। मेटल प्रदर्शन के वादे को पूरी तरह से पूरा करता है। एक तो, सिंगल थ्रेडेड डिस्पैच प्रदर्शन OpenGL की तुलना में स्पष्ट रूप से बेहतर है (कार्यभार के आधार पर हमारे रेंडर फ्रेम के ड्रॉ डिस्पैच हिस्से को 2-3 गुना तक छोटा करना), और यह इस बात को ध्यान में रखते हुए है कि हमारा OpenGL इम्प्लीमेंटेशन अनावश्यक स्टेट सेटअप को कम करने और फास्ट पाथ का उपयोग करके ड्राइवर के साथ बेहतर तालमेल बिठाने के मामले में काफी अच्छी तरह से ट्यून किया गया है। लेकिन यह यहीं नहीं रुकता - Metal में मल्टीथ्रेडिंग का उपयोग करना बहुत आसान है, बशर्ते आपका रेंडरिंग कोड इसके लिए तैयार हो। हमने अभी तक थ्रेडेड ड्रॉ डिस्पैच पर स्विच नहीं किया है, लेकिन हम पहले से ही संसाधनों को तैयार करने वाले कुछ अन्य हिस्सों को रेंडर थ्रेड से अलग करके चलाने के लिए परिवर्तित कर रहे हैं, जो OpenGL के विपरीत, काफी हद तक सहज है।

इसके अलावा, मेटल हमें आसानी से सुलभ और विश्वसनीय उपकरण देकर कुछ अन्य प्रदर्शन समस्याओं को ठीक करने की अनुमति देता है। हमारे रेंडरिंग कोड का एक केंद्रीय हिस्सा वह सिस्टम है जो CPU पर वर्ल्ड स्पेस में लाइटिंग डेटा की गणना करता है और इसे 3D टेक्सचर के क्षेत्रों में अपलोड करता है (जिसे हमें OpenGL ES 2 हार्डवेयर पर एमुलेट करना पड़ता है)। अपडेट आंशिक होते हैं इसलिए हम पूरी टेक्सचर को डुप्लिकेट नहीं कर सकते और हमें इस बात पर निर्भर रहना पड़ता है कि ड्राइवर glTexSubImage3D को कैसे लागू करता है। एक समय पर हमने अपडेट प्रदर्शन को बेहतर बनाने के लिए PBO का उपयोग करने की कोशिश की, लेकिन एंड्रॉइड और iOS दोनों पर हमें व्यापक स्थिरता समस्याओं का सामना करना पड़ा। मेटल पर, एक क्षेत्र को अपलोड करने के दो अंतर्निर्मित तरीके हैं - MTLTexture.replaceRegion, जिसका उपयोग आप तब कर सकते हैं जब GPU वर्तमान में टेक्सचर को नहीं पढ़ रहा हो, या MTLBlitCommandEncoder (copyFromBufferToTexture  या copyFromTextureToTexture), जो क्षेत्र को असिंक्रोनस रूप से अपलोड कर सकता है ताकि GPU ठीक समय पर टेक्सचर का उपयोग शुरू कर सके। 

ये दोनों तरीके मेरी अपेक्षा से धीमे थे। पहला तरीका वास्तव में उपलब्ध नहीं था क्योंकि हमें कुशल आंशिक अपडेट्स का समर्थन करना था, और यह पूरी तरह से CPU पर काम करता था, जिसमें पता अनुवाद का एक बहुत धीमा कार्यान्वयन प्रतीत होता था। दूसरा काम तो करता था लेकिन ऐसा लगता था कि यह 3D टेक्सचर को भरने के लिए 2D ब्लिट्स (2D blits) की एक श्रृंखला का उपयोग करता था, जिनके लिए CPU साइड पर कमांड सेट अप करना काफी महंगा था और किसी भी कारण से उनका GPU ओवरहेड भी बहुत अधिक था। अगर यह OpenGL होता तो यह खत्म हो जाता - वास्तव में, इन दोनों तरीकों का प्रदर्शन OpenGL में इसी तरह के अपडेट की देखी गई लागत के लगभग बराबर था। सौभाग्य से, चूंकि यह मेटल है, इसमें कम्प्यूट शेडर्स तक आसान पहुंच है - और एक बहुत ही सरल कम्प्यूट शेडर ने हमें बफ़र -> 3D टेक्सचर अपलोड करने की क्षमता दी जो CPU और GPU पर बहुत तेज़ था और मूल रूप से कोड के इस हिस्से में हमारी प्रदर्शन समस्याओं को हमेशा के लिए हल कर दिया((ये आंकड़े A10 पर प्रति फ्रेम अपडेट किए गए 128 KB डेटा (दो 32x16x32 RGBA8 क्षेत्र) के लिए हैं)):

अंतिम सामान्य टिप्पणी के रूप में, Metal कोड का रखरखाव भी काफी हद तक सहज है। अब तक हमें जो भी अतिरिक्त सुविधाएँ जोड़नी पड़ीं, उन्हें वहाँ जोड़ना हमारे द्वारा समर्थित किसी भी अन्य API की तुलना में आसान था, और मुझे उम्मीद है कि यह प्रवृत्ति जारी रहेगी। एक चिंता यह थी कि एक और API जोड़ने के लिए निरंतर रखरखाव की आवश्यकता होगी, लेकिन OpenGL की तुलना में इसके लिए वास्तव में बहुत अधिक काम की आवश्यकता नहीं होती है। वास्तव में, चूंकि हमें अब iOS पर OpenGL ES 3 का समर्थन नहीं करना है, इसका मतलब है कि हम अपने कुछ OpenGL कोड को भी सरल बना सकते हैं।

स्थिरता

आज iOS पर, मेटल बहुत स्थिर लगता है। मुझे यकीन नहीं है कि 2014 में लॉन्च के समय स्थिति कैसी थी, या आज मैक पर यह कैसी है, लेकिन iOS के लिए ड्राइवर और टूल दोनों काफी ठोस लगते हैं।

iOS 10 पर हमें एक ड्राइवर समस्या हुई थी जो Xcode 7 से संकलित शेडर्स को लोड करने से संबंधित थी (जिसे हमने Xcode 8 पर स्विच करके ठीक किया), और iOS 9 पर एक ड्राइवर क्रैश हुआ जो nextDrawable API के दुरुपयोग का परिणाम था। इसके अलावा हमने कोई व्यवहारिक बग या कोई क्रैश नहीं देखा है। एक अपेक्षाकृत नई API के लिए, मेटल हर मामले में बहुत ठोस रहा है।

इसके अतिरिक्त, Metal के साथ आपको जो टूल्स मिलते हैं वे विविध और समृद्ध हैं; विशेष रूप से, आप उपयोग कर सकते हैं:

  • एक काफी व्यापक सत्यापन परत जो API का उपयोग करने में सामान्य समस्याओं की पहचान करती है। यह मूल रूप से Direct3D डिबग की तरह है - जो Direct3D के लिए तो परिचित है लेकिन OpenGL की दुनिया में लगभग अज्ञात है (सिद्धांत रूप में, ARB_debug_callback को यह हल करना चाहिए; हालांकि, व्यवहार में, यह ज्यादातर अनुपलब्ध है और जब उपलब्ध भी होता है, तो बहुत मददगार नहीं होता)
  • एक काम करने वाला GPU डिबगर जो आपके द्वारा भेजी गई सभी कमांड्स को उनकी स्थिति, रेंडर टारगेट की सामग्री, टेक्सचर की सामग्री आदि के साथ दिखाता है। मुझे नहीं पता कि इसमें एक काम करने वाला शेडर डिबगर है या नहीं क्योंकि मुझे इसकी कभी ज़रूरत नहीं पड़ी, और बफ़र निरीक्षण थोड़ा आसान हो सकता है, लेकिन यह ज़्यादातर काम कर देता है।
  • एक काम करने वाला GPU प्रोफाइलर जो प्रति-पास प्रदर्शन आँकड़े (समय, बैंडविड्थ) और प्रति-शेडर निष्पादन समय भी दिखाता है। चूँकि GPU एक टाइलर है, आप निश्चित रूप से प्रति-ड्रॉ-कॉल टाइमिंग की उम्मीद नहीं कर सकते। इस स्तर की दृश्यता होना - खासकर iOS पर ग्राफिक्स API में किसी भी GPU टाइमिंग जानकारी की पूरी कमी को देखते हुए - बहुत बढ़िया है।
  • एक काम करने वाला CPU/GPU टाइमलाइन ट्रेस (मेटल सिस्टम ट्रेस) जो CPU और GPU रेंडरिंग वर्कलोड की शेड्यूलिंग दिखाता है (GPUView के समान लेकिन वास्तव में उपयोग में आसान), कुछ UI विचित्रताओं को छोड़कर।
  • एक ऑफ़लाइन शेडर कंपाइलर जो आपके शेडर सिंटैक्स को मान्य करता है, कभी-कभी आपको उपयोगी चेतावनियाँ देता है, और आपके शेडर को एक बाइनरी ब्लॉब में परिवर्तित करता है जो रनटाइम पर लोड होने में काफी तेज़ है और पहले से ही उचित रूप से अनुकूलित होता है (इस प्रकार लोड समय कम हो जाता है क्योंकि ड्राइवर कंपाइलर तेज़ हो सकता है)।

यदि आप Direct3D या कंसोल की दुनिया से आते हैं, तो आप इनमें से हर एक चीज़ को सामान्य मान सकते हैं। मेरा विश्वास करें, OpenGL में इनमें से हर एक चीज़ असामान्य है और इसे उत्साह से देखा जाता है, खासकर मोबाइल पर जहाँ आप कभी-कभी खराब ड्राइवरों, बिना किसी सत्यापन, बिना किसी GPU डिबगर, बिना किसी सहायक GPU प्रोफाइलर, GPU शेड्यूलिंग डेटा इकट्ठा करने की क्षमता के बिना, और एक टेक्स्ट-आधारित शेडर भाषा के साथ काम करने के लिए मजबूर हैं जिसके लिए प्रत्येक विक्रेता का एक थोड़ा अलग पार्सर होता है।

निष्कर्ष

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

यदि आप यूनिटी या UE4 जैसे किसी थर्ड-पार्टी इंजन का उपयोग कर रहे हैं, तो मेटल को पहले से ही सपोर्ट किया जाता है; यदि आप ऐसा नहीं कर रहे हैं और आपको ग्राफिक्स प्रोग्रामिंग पसंद है या आप प्रदर्शन के बारे में बहुत गंभीर हैं और iOS या Mac को गंभीरता से लेते हैं, तो मैं आपको मेटल को आज़माने के लिए पुरजोर सलाह देता हूँ। आप निराश नहीं होंगे।