السلام عليكم ورحمة الله،
تحدثنا في الحلقات السابقة عن التوازي بين المعالجين (الرئيسي والرسومي)، كما تحدثنا عن التوازي داخل معالج الرسوميات نفسه. واليوم، سأتحدث عن واحدة من قواعد الأداء الأساسية في برمجة الرسوميات. تدعى (جمــّـع) أو (Batch) بالإنجليزية. لكن قبل الدخول بالتفاصيل دعوني أقدم لكم:
الرسام الأعمى
ربما من أفضل الأوصاف لمعالج الرسوميات هو أنه رسام أعمى. يمكنك اعتباره آلة تحمل فرشاة، لكن هذه الآلة لا تعرف شكل تلك الفرشاة ولا لونها. اعتبر نفسك أنت المسؤول عن إعطاء هذا الرسام الفرشاة المناسبة باللون المناسب، ومن ثم تهمس في أذنه بأن يحرك يده يمنة ويسرة في مكان ما على اللوحة، فتبدأ الألوان بالظهور هناك. ثم إذا أردت تغيير اللون، فستسحب الفرشاة من يد هذا الرسام، وتعطيه أخرى بديلة باللون الجديد، وتعيد الكرة.
في الحقيقة هذا ما نفعله نحن مبرمجو الرسوميات. مهمتنا هي إعداد حالة معالج الرسوميات بشكل صحيح قبل أن نطلب منه القيام برسم مجموعة من المثلثات. فأنت تحدد له ما المظللات التي تريد استخدامها، والإكساءات المصاحبة لها، وسطح الرسم المستهدف، وخيارات دمج ألفا، وخيارات اختبار العمق، و و و… كل ذلك يجب أن يتم قبل طلب رسم المثلثات. تدعى هذه العملية بـ (ضبط الحالة) أو بالإنجليزية (state setup) والمعنى أنك تجهز معالج الرسوميات ليكون بحالة معينة بحيث أن كافة طلبات الرسم التالية ستتم باستخدام تلك الحالة.
ثم إذا أردت رسم مجموعة أخرى من المثلثات لكن بمواصفات مختلفة (لون آخر، أو مظلل آخر، …الخ) فعليك إعادة ضبط حالة معالج الرسوميات قبل طلب رسم المجموعة الجديدة من المثلثات… وهكذا.
مما يظهر لنا الآن، أننا سنحتاج لإعادة ضبط الحالة لكل مجموعة مثلثات تختلف عن سابقتها بشيء ما. لو عدنا لمثال الرسام الأعمى، تخيل معي لو أنك تريد منه تلوين 100 منطقة، ولكل واحدة منها بدلت له فرشاة الرسم… سيكون ذلك أبطأ بكثيييير مما لو أعطيته الفرشاة مرة واحدة وطلبت منه تلوين الـ 100 منطقة باستمرار. لكن هل نستطيع فعل ذلك؟ ماذا لو أن كل منطقة من هاته المناطق فعلاً ذات لون مختلف؟ ألا يوجد حل؟ ربما لو أعطيناه فرشاة بوجهين بلونين مختلفين لاختصرنا نصف الجهد… هذه هي -يا أصدقائي- واحدة من أهم المسائل التي على مبرمج الرسوميات حلها…
إن كلفة تغيير حالة معالج الرسوميات تنقسم على كل من معالج الرسوميات والمعالج المركزي كذلك. فمن جهة، على المعالج المركزي كتابة الأوامر اللازمة لطابور الأوامر من أجل إجراء التغييرات على الحالة، ومن جهة أخرى على معالج الرسوميات استهلاك وتنفيذ هذه الأوامر. كل هذا العمل يحتاج إلى وقت لتنفيذه، وكما تقول القاعدة الذهبية في تحسين الأداء: أسرع كود هو الكود الذي لا يـُـنـفـّـذ! لذلك علينا تفادي هذا العمل قدر الإمكان… والحل؟
متحدين نسرع، متفرقين… نبطئ!
بعد الاعتذار للأستاذ أحمد الشقيري على الاستعارة… دعوني أشرح ما الفكرة. المسألة هي أنه لدينا مشهد ثلاثي الأبعاد، يتألف من عدد ما من المجسمات والمؤثرات الخاصة، ونريد من معالج الرسوميات حساب وإظهار هذا المشهد بأسرع ما يمكن، لكننا نعرف أن تغيير حالة معالج الرسوميات مكلفة. لذلك يجب إيجاد وسائل للحد من عدد التغييرات التي نقوم بها… جميع الوسائل التي سنطرحها هنا تدور حول فكرة أساسية هي “التجميع” (batching). والمعنى من هذا المصطلح هو تجميع كافة طلبات الرسم المتشابهة مع بعضها البعض في حزمة واحدة تشترك في نفس حالة معالج الرسوميات، مما يعني عدد أقل من عمليات التغيير على الحالة… إذ أن أسوأ ما نفعله هو تغيير حالة الرسوميات لكل مجموعة مثلثات نرسمها.
هناك عدة أساليب تساعدنا في تحقيق التجميع. سأذكر ثلاث منها في هذه التدوينة بترتيبها من الأبسط للأعقد:
1- الترتيب (Sorting)
مثلاً. لدينا مشهد قتالي في غابة (تخيل معي أحد مشاهد لعبة Crysis). لدينا أغصان الأشجار وأوراق الأشجار والشخصيات والأسلحة والأرض والسماء. قد يقوم الكود التقليدي برسم الأجسام بترتيبها المنطقي. مثلاً، الأرض ثم السماء، ثم الأشجار واحدة واحدة، ولكل منها يرسم الأغصان أولاً ��م أوراقها. يلي ذلك كل شخصية مع سلاحها. لكن هذا يفتقد للفاعلية، فمثلاً لو لدينا 50 شجرة ظاهرة فهذا يعني 100 أمر رسم (50 للأغصان و 50 للأوراق). وكل مرة سنغير حالة المعالج، تارة لرسم أغصان وتارة لرسم أوراق، وبالتالي 100 تغيير.
أسلوب الترتيب ينص على أن نرتب أوامر الرسم وفقاً لحالة الرسم (sort by state). فتعطى الحالات أوزاناً مختلفة تعبر عن كلفة تغييرها (مثلاً، تغيير المظلل بالدرجة الأولى لأنه مكلف جداً، يليه تغيير إكساء، ثم حالة العمق، ثم حالة دمج ألفا، ثم مخازن الرؤوس والفهارس). النتيجة أن أوامر الرسم الآن ستصبح مرتبة بحيث فروق الحالة بينها أصغرية، فالتغييرات أقل عموماً.
بالعودة للمشهد آنف الذكر، ستصبح كافة أغصان الأشجار مرتبة بالتسلسل، تليها الأوراق. وبذلك ينحدر عدد التغييرات من 100 إلى 2 فقط!
لتطبيق هذا الأسلوب، ستحتاج لإنشاء بنية خاصة تمثل “أمر رسم” ورصه في طابور يتم ترتيبه وفقاً للمنهاج المذكور هنا قبل بدء تنفيذ الأوامر. كما ستحتاج إلى تعديل المحرك ليتفادى إعادة ضبط الحالة على معالج الرسوميات إن تساوت بين أمر الرسم الحالي والسابق. تدعى هذه العملية بترشيح الحالات (state filtering).
2- الضم والدمج (Merging & Combining)
هذا الأسلوب يعتمد على استخدام الأسلوب السابق إلى حد ما.
لو أخذنا الأشجار من المثال السابق… قد يكون بعضها من أشجار القيقب* صفراء الأوراق، وبعضها من أشجار العنب خضراء الأوراق، وبعضها حمراء الأوراق ... الخ. قد يكون الرسام قد وضع الورقة الخاصة بكل نوع من الأشجار في إكساء مستقل. فلدينا إكساء يحمل ورقة صفراء، وآخر خضراء، وآخر حمراء. ثلاث إكساءات… هذه الأوراق سيتم رسمها على ثلاث دفعات لأنها تختلف فيما بينها بالإكساء. لكن ماذا لو طلبنا من الرسام وضع الأوراق الثلاثة في إكساء واحد بجانب بعضها البعض؟ سنستطيع عندها رسم هذه جميع الأوراق دون تغيير الحالة فيما بينها… المزيد من التوفير!
فلنخطو خطوة إضافية للأمام أيضاً… لو طلبنا من نفس الرسام استخدام إكساء واحد لكافة الأوراق والأغصان أيضاً، ودمج الأوراق والأغصان في مجسم واحد، نستطيع الآن رسم الأشجار جميعها (أغصاناً وأوراقاً) دون تغيير الحالة. طبعاً هناك ضوابط وحدود تمنعنا من دمج كل شيء مع كل شيء آخر (ستكتشفها بنفسك عند تطبيق الفكرة). لكن تطبيق هذا الأسلوب قدر الإمكان له فوائد إيجابية في تقليل عدد تغييرات الحالة.
(*) لا تعرف ما هو القيقب؟ هو الاسفندان... ماذا؟ لا تعرف ما هو الاسفندان؟ باختصار هو أحد أنواع الأشجار التي تنتمي للفصيلة الصابونية، وقد اتخذت دولة كندا من ورقته رمزاً لها.
3- الاستنساخ (Instancing)
الأسلوب الأخير الذي سنتحدث عنه اليوم وأكثرها تقدماً هو الاستنساخ، وهو ميزة تدعمها معالجات الرسوميات الحديثة (بدءاً من مظللات الجيل الثالث). هذه الميزة تمكن المبرمج من رسم عدد كبير من المجسمات المختلفة بأمر رسم واحد وبحالة رسم واحدة، مما يوفر الكثير من الوقت على كل من المعالج المركزي والرسومي!
تعتمد فكرة الاستنساخ على تقسيم بيانات الرسم إلى قسمين: قسم المعلومات المشتركة بين جميع النسخ، وقسم المعلومات الخاصة بكل نسخة.
لنعود لمثال الأشجار، المعلومات المشتركة بين كل النسخ هو مجسم الأغصان والأوراق. المعلومات الخاصة بكل نسخة هي موقع الشجرة في العالم وتوضعها وحجمها ونوع أوراقها.
نضع المعلومات المشتركة في مخزن رؤوس واحد، والمعلومات الخاصة بكل نسخة يتم سردها بالتسلسل في مخزن رؤوس آخر واحد فقط.
الآن وقت السر:
يتم ربط هذه المخازن مع جهاز الرسم، ويتم إخبار معالج الرسوميات بأن يكرر كافة المعلومات المشتركة مرة لكل عنصر في مخزن رؤوس المعلومات الخاصة! بهذا الإعداد سيتم استدعاء مظلل الرؤوس عدد من المرات يساوي مضروب عدد الرؤوس في مخزن المعلومات المشتركة بعدد الرؤوس في مخزن المعلومات الخاصة. النتيجة؟ سيقوم معالج الرسوميات تلقائياً برسم كاااافة الأشجار بأماكنها وأنواعها الصحيحة بأمر رسم واحد!
للأسف يصعب شرح هذا الأسلوب بالتفصيل في هذه التدوينة (ربما في تدوينة أخرى) لكني أدعوك للاستزادة في التعرف عليه من المقالات والوثائق المرفقة مع دايركت ثري دي أو أوبن جي إل.
من الجدير بالذكر أن لعبة هايبر فويد تعتمد بشدة على هذا الأسلوب لرسم المؤثرات الخاصة المختلفة فيها كالانفجارات والسدم بل وحتى النصوص على الشاشة!
بهذا القدر نكتفي اليوم، ونلتقي ثانية بإذن الله في حلقة قادمة…
السلام عليكم ورحمة الله!