بسم الله الرحمن الرحيم
يوما ما كنت قد أكملت لعبتك أو برنامج الرسومي, و لكن ظهر الأسوء في ذلك, بطء شديد و تشوه في العرض.
سأفسر هذا:
الشيفرة البرمجية ممكن أن تكون صغيرة و لكن لها طريق طويل للوصول إلى الشاشة. سيقوم السيد أوبن جي أل بتحميل البيانات (بيانات النقاط و الألوان و إحداثيات الإكساء ووو ) إلى الذاكرة , وفي كل مرة يدخل تطبيقك أو لعبتك الحلقة يعيد السيد أوبن جي أل تحميل تلك البيانات إلى الذاكرة مرة أخرى.ففي حالة عمل مجسمات متحركة ( مثلا: علم يرفرف أو سطح البحر أو أو كوكب يدور أو كل مجسم يتطلب الحركة تلقائيا ) و بإحدى الأنواع التالية:
النوع الأول:عمل مجسمات متحركة, يتم تحديث بياناتها عن طريق دوال يوفرها مكتبة السيد أوبن جي أل.
النوع الثاني: عمل مجسمات متحركة, يتم تحديث بياناتها عن طريق دوال نوفرها نجن في تطبيقاتنا.
النوع الثالث: عمل مجسمات ثابتة, مثل الجدران و الأرض و كل شيء ثابت لا يتحرك و بمعنى أخر ليس لهذا المجسم بيانات متغيرة.
ففي النوع الأول و النوع الثالث يكمل الإشكال, بحيث أن السيد أوبن جي أل , سيقوم بإرهاق نفسه جراء التحميل المتكرر للبيانات من ذاكرة النظام نحو ذاكرة بطاقة الرسوميات.
أما النوع الثاتي فلا بأس به و يمكن إعتباره حل مقبول و لكن إن إستعملت ما نحن بصدده فذلك سيزيد من كفاءة التطبيق و يجعله متصلا إتصالا تاما مع بطاقة الرسوميات "graphic card".
و الحل هو إستعمال كائن الذاكرة "buffer object". إن كائن الذاكرة له ميزات عديدة منها:
-جعل البيانات مثل مصفوفة النقاط و مصفوفة الألوان و و و إلخ قرب بطاقة الرسوميات و بعيدا عن إدارة النظام.
- سيتم التقليل من عملية التحميل من ذاكرة النظام نحو ذاكرة بطاقة الرسوميات, بحيث أنه سيتم معالجة البيانات بشكل أسرع مما كان عليه سابقا.
(قفزة سريعة نحو مصفوفة النقاط"vertex array") لقد أصبحت مصفوفة النقاط جوهر و لب النسخة 1.1, مع إضافة بعض التغييرات المحسنة في النسخة 1.4, فقبل النسخة 1.5 كانت التطبيقات قادرة على تخزين بيانات مصفوفة النقاط فقط في العميل "client" إلى أن ظهرت النسخة 1.5 و التي كان بمقدورها تخزين البيانات كمصفوفة التقاط "vertex array" في الخادم "server".
( يطلق إسم الخادم على الجهة الخاصة بذاكرة بطاقة الرسوميات "VRAM" و العميل على الجهة الخاصة بذاكرة النظام "RAM" دلالة على نوع الذاكرة المستعملة.
إنشاء و تفعيل كائن الذاكرة:
1- إنشاء كائن الذاكرة , بحيث سيقوم الأوبن جي أل بتعريف معرفات تخص الذاكرة المرغوب فيها و ذلك عن طريق الدالة التالية:
void glGenBuffers( GLsizei n, GLuint * buffers);
البارميتر الأول "n" عدد المعرفات المرغوب فيها .و البارميتر الثاني "bufffers" هو مؤشر نحو عنوان مصفوفة المعرفات المرغوب فيها.
مثال: تخيل أتك أردت إنشاء أربع كائنات ذاكرة"4buffers object" لتخزين البيانات عليها ( بيانات النقاط و بيانات المواظم و بيانات إحداثيات الإكساء و بيانات المفهرسات ). الشيفرة التالية تقوم بإنشاء معرفات لأربع كائنات ذاكرة:
GLuint bufObjects[4];
glGenBuffers(4,bufObjects);
و لإلغاء كائن الذاكرة تماما من ذاكرة الخادم نستدعي هذه الدالة
void glDeleteBuffers( GLsizei n,const GLuint * buffers );
-ثانيا:
تأتي مرحلة ربط التطبيق أي ربط الخادم مع العميل , و لعمل ذلك نقوم بإستدعاء الدالة التالية:
void glBindBuffer( GLenum target, GLuint buffer );
البراميتر الأول "target" يمثل نمط أو أسلوب المعالجة و يأخذ أحد الأنماط التالية:
ARRAY_BUFFER خاص بالنقاط
COPY_READ_BUFFER خاص بالتعامل مع الذاكرة
COPY_WRITE_BUFFER خاص التعامل مع الذاكرة
ELEMENT_ARRAY_BUFFER خاص بالمفهرسات
PIXEL_PACK_BUFFER خاصة بالبكسلات
PIXEL_UNPACK_BUFFER خاصة بالبكسلات
TEXTURE_BUFFER تخص الإكساء
TRANSFORM_FEEDBACK_BUFFER خاص بالتغدية الإسترجاعية (يخص مجالات النقر و تحديد المجسمات في عالم الأوبن جي أل)
UNIFORM_BUFFER خاص بالذاكرة المنتظمة (جديد النسخة أوبن جي أل 3.0 و النسخ الأحدث لمكتبة الأوبن جي أل)
البراميتر الثاني "buffer" و هو جزء من مصفوفة المعرفات , لو أنك قمت بإستخدام معرف النقاط مثلا , فإن هذا البراميتر "buffer" سيأخد هذا الشكل bufObjects[0] و تريد ثانية أن تستخدم معرف النواظم bufObjects[1] و هكذا دواليك. في حالة الرغبة في التخلص من كائن الذاكرة نمرر له القيمة 0.
-ثالثا:
الأن و قد أصبحت كائنات الذاكرة نشطة و في إنتظار التهيئة للبيانات , فالأن سنققوم بتحميل البيانات ( مصفوفة النقاط و مصفوفة النواظم ووو) بداخل كائن الذاكرة ( موجود في الخادم كما ذكرت سابقا ) , هذا ما يجعل هذه البيانات أقرب و أقرب لمعالج بطاقة الرسوميات , و لعمل ذلك نستدعي الدالة التالية:
void glBufferData( GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage );
هذه الداله الدالة تمكنك من إنشاء حجم مناسب لكائن الذاكرة و تحميل البيانات بداخله . فالبراميتر الثاني "size" هو مقاس البيانات, و البراميتر الثالث هو "data" هو مؤشر لعنوان مصفوفة تحتوي على بيانات, البراميتر الرابع "usage" هو نمط أو أسلوب للتعامل مع البيانات و بشكل أخر فإنه يقوم بتحديد نوع من أحد الأنواع التي تطرقناها سابقا و يأخذ أحد الأشكال التالية:
STREAM_DRAW
STREAM_READ
STREAM_COPY
STATIC_DRAW
STATIC_READ
STATIC_COPY
DYNAMIC_DRAW
DYNAMIC_READ
DYNAMIC_COPY
STREAM: هذا يفيد أن البيانات سيتم تحميلها من العميل نحو الخادم مرة واحد في كل عملية تصيير, مثلا تستعمل في الأجسام المتحركة (أي مثلا المجسمات التي لديها مصفوفة يتغير محتواها في كل عملية التصيير).
STATIC: هذا يفيد أن البيانات سيتم تحميلها من العميل إلى الخادم مرة واحدة فقط أي لن يتغير محتوى البيانات الموجودة في الخادم إلى نهاية التطبيق.
DYNAMIC: هذا يفيد في عملية التمرير المتعدد MULTIPASS أي أن البيانات سيتم تحميلها من العميل نحو الخادم مرات عديدة في كل عملية تصيير مثلا.
( تتم عملية التصيير بإستدعاء أحد الدوال الخاصة بالتصيير مثل glDrawArrays و glDrawElements ووووو إلخ )
READ:يسمح بقراءة البيانات التي يديرها الأوبن جي أل.
COPY: يسمح بعرض مجسم من جزء مصدره يديره الأوبن جي أل.
DRAW: تسمح بتصيير أو عرض المجسم من قبل دوال العرض glDrawArrays.
-رابعا:
و الأن بعدما قمنا بتحميل البيانات من العميل نحو الخادم, نريد فجأة أن نضيف بعض البيانات إلى كائن الذاكرة, مثلا نرد إضافة بيانات المفهرسات .فماذا نفعل؟
إن فكرة إعادة تحميل كائن الذاكرة بالكامل من جديد مع الإضافات الجديدة المرغوب فيها هي فكرة ليست قوية جدا لأن الأوبن جي أل سيرهق بعمل أشياء متكررة, لذا و لحل هذه المشكلة , أوجدت مكتبة السيد أوبن جي أل الدالة glBufferSubData , هذه الدالة تغني عنك عناء التحميل المتكرر للبيانات إلى الخادم , فعوضا عن ذلك, ستتمكن من إضافة البيانات إلى كائن الذاكرة و من غير إعادة تحميل جميع البيانات السابقة الموجودة على كائن الذاكرة.
void glBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data );
البراميتر الثاني "offset" هو قيمة تشير إلى لموضع البدء في وضع البيانات على كائن الذاكرة, و هو كل ما سنحتاجه هنا في هذه الدالة.
-خامسا:
تخيل أنك بحاجة لقيم البيانات الموجود في كائن الذاكرة أو أردت تحديث البيانات بشكل كامل, و لهذا السبب أوجدت مكتبة الأوبن جي أل دالة glMapBuffer , هذه الدالة تخبر السيد أوبن جي أل بأن التطبيق ينوي تحديث البيانات و تقول للسيد أوبن جي أل " يا سيدي هلا توقفت برهة عن معالجة البيانات ريثما ينتهي تحديثها ", و هذا كله لتفاذي التضارب في البيانات و خلطها.
إن الدالة glMapBuffer ترجع مؤشر نحو كائن الذاكرة فهذا ما يسهم في تقديم إمكانية التعديل على البيانات الموجودة في كائن الذاكرة أو أخذ البيانات منها لمعالجتها في أمور أخرى.
void * glMapBuffer( GLenum target, GLenum access );
البراميتر الثاني "access" هو كيفية الوصول للبيانات في كائن الذاكرة من أجل معالجتها, و هذا البراميتر يأخذ أحد الخيارات الثلاثة:
GL_READ_ONLY الوصول إلى البيانات للقراءة منها فقط
GL_WRITE_ONLY الوصول إلى اللبيانات للكتابة عليها فقط
GL_READ_WRITE الوصول إلى البيانات للكتابة عليها أو للقراءة منها
-سادسا:
لنفترض أنك تريد عمل تحديث للبيانات الموجودة في كائن الذاكرة بشكل جزئي و ليس كامل, مثلا أنك تريد تغيير جميع القيم الموجودة في كائن الذاكرة الخاصة بالإحداثيي ع "z coordinate" فقط. الدالة التالية توفر لك ذالك.
GLvoid *glMapBufferRange(GLenum target, GLintptr offset, GLsizeIptr length, GLbitfield access);
البراميتر الرابع "access" سيأخذ القيم التالية (بإمكان الجمع بين هذه التعليمات عن طريق العملية "|"):
MAP_READ_BIT لقراءة البيانات
MAP_WRITE_BIT للكتابة على البيانات
MAP_INVALIDATE_RANGE_BIT جعل جزء من كائن الذاكرة ( مثل بيانات الألوان و بيانات النقاط ) في حالة غير معرفة أي تعطيل مؤقت و بمعنى أخر إبطال مفعول بيانات هذا الجزء من بيانات كائن الذاكرة
MAP_FLUSH_EXPLICIT_BIT تحديث جزء من البيانات في كائن الذاكرة و إستبدالها ببيانات يتم تحديدها عن طريق الدالة glFlushMappedBufferRange()
MAP_INVALIDATE_BUFFER_BIT إبطال مفعول بيانات كائن الذاكرة بالكامل ( هنا لا يعني أن الغاية هي مسح البيانات بل تعطيلها فقط )
MAP_UNSYNCHRONIZED_BIT جعل كائن الذاكرة يعمل على نمط الغير متزامن , يعني إمكانبة التخلص من القيم السابقة و الحفاظ على القيم الأخرى من ضمنها القيم الحالية داخل كائن الذاكرة
GLvoid glFlushMappedBufferRange( GLenum target, GLintptr offset, GLsizeiptr length );
هذه الدالة تفيد في أن القيم داخل الجزء (أي جزء من البيانات بطبيعة الحال) الذي تم النأشير عليه في كائن الذاكرة قــــد وقع عليها تعديل, مما قد يؤدي بحادم الأوبن جي أل بتحديث النسخة المخبئة لكائن الذاكرة.
boolean glUnmapBuffer( GLenum target );
هذه الدالة ترجع القيمة البولونية "GL_TRUE" إذا ما تم تعطيل التأشير على جزء من كائن الذاكرة أو كائن الذاكرة بأكمله و العكس صحيح . هذه الدالة ضرورية للخروج حين الإنتهاء من التعديل على البيانات (أي إنهاء مهمة الدالة glMapBuffer ).
ملاحظة:
يجب الحذر على أن تكون قيم البداية و الحجم صحيحين ( يعني قيمة البداية أوالحجم يساويان قيمة سالبة أو قيمة البداية جمع قيمة الحجم أكبر من جزء كائن الذاكرة ) و إلا سيتولد الخطأ GL_INVALID_OPERATION.
مثال:
GLfloat* data;
data = (GLfloat*) glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
if (data != (GLfloat*) NULL) {
for( i = 0; i < 8; ++i )
data[3*i+2] *= 2.0; /* التعديل على قيم المحور ص Z */
glUnmapBuffer(GL_ARRAY_BUFFER);
} else {
/* لاتوجد إمكانية تحديث البيانات */
}
GLvoid glCopyBufferSubData(GLenum readbuffer, GLenum writebuffer, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);
تقوم هذه الدالة بنسخ جزء من كائن الذاكرة كبيانات النقاط مثلا أو بيانات إحداثيات الإكساء أو أو ...إلخ نحو جزء أخر من كائن الذاكرة.البراميتر الأول "readbuffe" نوع القراة من كائن الذاكرة و البراميتر الثاني "writebuffer" هو نوع الكتابة في كائن الذاكرة و البراميتر الثالث "readoffset" هو عنوان للمكان (أي مكان البيانات) المراد القراءة منه في كائن الذاكرة و البراميتر الرابع "writeoffset" هو عنوان للمكان المراد وضع أو كتابة البيانات ( البيانات المقروء من الجهة الأخرى ) فيه و البراميتر الخامس هو حجم البيانات المقروؤة أو المكتوبة.
إحتمال ظهور كوارث لو أنك تعمدت مثلا القراءة من بيانات النقاط و النسخ في بيانات النواظم , و الصواب هو القراءة من بيانات النقاط و الكتابة في بيانات النقاط أو القراءة من بيانات النواظم و الكتابة على بيانات النواظم , هل هذا مؤكدة!
GLboolean glIsBuffer( GLuint buffer );
هذه الدالة تقوم بفحص ما إذا كان هناك كائن ذاكرة معرف مسبقا , فإن وجدت ستعيد هذه الدالة القيمة "GL_TRUE" و العكس إن لم تجد.
نظرة سريعة حول كائنات مصفوفة النقاط"vertex buffer object":
و الأن أنه أصبح بمقدورك عمل مشاريع تظاهي في السرعة والأناقة كتلك التي نراها في ألعاب فاينل فانتزي مثلا , و لكن في عالم البرمجيات و بالذات في عالم الأوبن جي أل مازال هنالك المزيد و المزيد من الشيفرات و الدوال مستقبلا (إن شاء الله) التي تولد ثبوتية و مرونة في قمة الأعمال الأكثر من رائعة , و الأن قد أصبح التعامل مع مصفوفة النقاط"vertex object" سهل و سريع للغاية , إذ تم دمج كائنات الذاكرة مع مصفوفة النقاط معا لتولد ما يسمى كائنات مصفوفة النقاط"vertex buffer object".
لتهيئة كائن مصفوفة النقاط , نقوم بإستدعاء الدالة التالية:
void glGenVertexArrays(GLsizei n, GLuint *arrays);
البراميتر الأول "n" يعبر عن عدد المصفوفات المرغوب فيها, مثلا إذا كان n = 3 يعني أننا سنستعمل مثلا مصفوفة النقاط و مصفوفة الألوان و مصفوفة إحداثيات الإكساء. كان هذا مثالا.
البراميتر الثاني "arrays" هو عنوان للمصفوفة المرغوب فيها.
يتم إلغاء كائن مصفوفة النقاط عن طريق إستدعاء الدالة التالية :
void glDeleteVertexArrays(GLsizei n, GLuint *arrays);
الأن تأتي مرحلة ربط العميل مع الخادم و تفعيل كائن مصفوفة النقاط ليكون قابل للمعالجة في الخادم , و لعمل ذلك نستدعي الدالة التالية:
GLvoid gBindVertexArray(GLuint array);
البراميتر الأول "array" هو إسم كائن مصفوفة النقاط المرسلة للخادم.
أشياء من الضروري تجنبها:
1- تجنب إستدعاء الدالة glVertexPointer أكثر من مرة في كل كائن ذاكرة النقاط, فهذه الدالة لديها الكثير من الإعدادات هناك, هكذا سنتمكن من تجنب التكرار . أغلب الطرق الفعالة لذلك هو ربط ذاكرة مصفوفة النقاط مع الخادم , ثم إستدعاء الدالة glVertexPointer , هكذا تكون هذه الدالة تستدعى مرة واحدة في كائن مصفوفة النقاط . على فكرة , قد تضن أن عملية الربط هي من أساسيات إدارت كائن الذاكرة , لكن الحقيقة تقول عكس ذلك, فأنظمة كائن الذاكرة تقوم بالإنتظار لغاية الإنتهاء من العمليات المهمة بالنسبة لها عند كل وضع لدالة glBindBuffer. هذه النصيحة تناسب جميع الوظائف الأخرى في العمل بنفس الطريقة كدالة glVertexPointer.
2- كنا نرغب في إستعمال glMapBuffer من أجل تحديث مجموعة كاملة من البيانات , و لسوء الحظ هذه العملية هي أشد كفاءة عن نظيرتها glBufferData, بالإضافة إلى الحديث عن سواقة الأوبن جي أل الخاص ببطاقة الرسوميات فهو لا يعرف مطلقا مالغرض من كوننا نريد عنوان الذاكرة المرجعة بواسطة الدالة glMapBuffer , هل سنقوم بتغيير بعض البيانات أم نريد تغيير البيانات الموجودة في هذه الذاكرة بأسرها؟ , هنالك إحتمال أن معالج بطاقة الريوميات "GPU" منهمكا في معالجة البيانات , و لكي نطلب البيانات للتحديث سوف نقوم بإجبار السواقة "driver" بالإنتظار ريثما ينتهي معالج الرسوميات "GPU" من إنهاء عمله.
و لحل هذه المشكلة , سنقوم بإستدعاء دالة glBufferData مع القيمة NULL للعنوان ثم نقوم بإستدعاء الدالة glMapBuffer , هكذا نقول للسواقة "driver" أن البيانات السابقة غير صالحة , عنها ترجع لنا الدالة glMapBuffer عنوان أو مؤشر جديد يمكننا إستخدامه, في حين يبقى معالج الرسوميات "GPU" يعمل على البيانات السابقة.
مثال عن إستعمال كائن مصفوفة النقاط (من كتاب REDBOOK ):
#define BUFFER_OFFSET(offset) ((GLvoid*) NULL + offset)
#define NumberOf(array) (sizeof(array)/sizeof(array[0]))
typedef struct {
GLfloat x, y, z;
} vec3;
typedef struct {
vec3 xlate; /* Translation */
GLfloat angle;
vec3 axis;
} XForm;
enum { Cube, Cone, NumVAOs };
GLuint VAO[NumVAOs];
GLenum PrimType[NumVAOs];
GLsizei NumElements[NumVAOs];
XForm Xform[NumVAOs] = {
{ { -2.0, 0.0, 0.0 }, 0.0, { 0.0, 1.0, 0.0 } },
{ { 0.0, 0.0, 2.0 }, 0.0, { 1.0, 0.0, 0.0 } }
};
GLfloat Angle = 0.0;
void
init()