لا تعد البرمجة الرسومية مصدرًا للمتعة فحسب ، ولكنها أيضًا مصدر إحباط عندما لا يتم عرض شيء ما كما هو مقصود أو لا يتم عرض أي شيء على الشاشة على الإطلاق. بالنظر إلى أن معظم ما نقوم به يتعلق بمعالجة البيكسل ، فقد يكون من الصعب معرفة سبب الخطأ عندما لا يعمل شيء كما يجب. تصحيح هذا النوع من الخطأ أكثر صعوبة من تصحيح الأخطاء على وحدة المعالجة المركزية. ليس لدينا وحدة تحكم يمكننا من خلالها إخراج النص ، ولا يمكننا وضع نقطة توقف في التظليل ولا يمكننا فقط أخذ حالة البرنامج والتحقق منها على GPU.
في هذا البرنامج التعليمي ، سوف نقدم لك بعض أساليب وتقنيات تصحيح الأخطاء لبرنامج OpenGL. تصحيح الأخطاء في برنامج OpenGL ليس بالأمر الصعب ، وتعلم بعض الحيل سيؤتي ثماره بالتأكيد.
محتوىالجزء 2. الإضاءة الأساسية الجزء 4. ميزات OpenGL المتقدمة الجزء 5. الإضاءة المتقدمة glGetError ()
عندما تستخدم OpenGL بشكل غير صحيح (على سبيل المثال ، عند إعداد مخزن مؤقت وتنسى ربطه) ، فسوف تلاحظ OpenGL وإنشاء علامة خطأ مخصصة أو أكثر خلف الكواليس. يمكننا تتبع هذه الأخطاء عن طريق استدعاء وظيفة glGetError()
، التي تقوم ببساطة بفحص مجموعة علامات الخطأ وإرجاع قيمة الخطأ في حالة حدوث أخطاء.
GLenum glGetError();
هذه الدالة تقوم بإرجاع علامة خطأ أو عدم وجود خطأ على الإطلاق. قائمة القيم المرجعة:
داخل الوثائق الخاصة بوظائف OpenGL ، يمكنك العثور على رموز الأخطاء التي يتم إنشاؤها بواسطة الوظائف التي يتم استخدامها بشكل غير صحيح. على سبيل المثال ، إذا نظرت إلى وثائق دالة glBindTexture()
، يمكنك العثور على رموز الأخطاء التي تم إنشاؤها بواسطة هذه الوظيفة في قسم الأخطاء.
عند تعيين علامة الخطأ ، لن يتم إنشاء علامات خطأ أخرى. علاوة على ذلك ، عندما يتم استدعاء glGetError
، تمحو الوظيفة كل علامات الخطأ (أو واحدة فقط على نظام موزع ، انظر أدناه). هذا يعني أنه إذا اتصلت بـ glGetError
مرة واحدة بعد كل إطار glGetError
خطأ ، فهذا لا يعني أن هذا هو الخطأ الوحيد وما زلت لا تعرف مكان حدوث هذا الخطأ.
لاحظ أنه عندما يعمل OpenGL بطريقة موزعة ، كما هو الحال غالبًا على أنظمة X11 ، يمكن إنشاء أخطاء أخرى أثناء وجود رموز مختلفة. استدعاء glGetError
ثم ببساطة مسح واحد فقط من علامات رمز الخطأ بدلاً من الكل. لهذا السبب ، يوصون باستدعاء هذه الوظيفة في حلقة.
glBindTexture(GL_TEXTURE_2D, tex); std::cout << glGetError() << std::endl;
من الميزات المميزة لـ glGetError
أنه يسهل نسبياً تحديد مكان حدوث أي خطأ والتحقق من استخدام OpenGL بشكل صحيح. دعنا نقول أنك لا ترسم أي شيء ، ولا تعرف ما هو السبب: تم تعيين المخزن المؤقت للإطار بشكل غير صحيح؟ نسيت أن ضبط الملمس؟ عن طريق الاتصال بـ glGetError
كل مكان ، يمكنك اكتشاف مكان حدوث الخطأ الأول بسرعة.
بشكل افتراضي ، تقوم glGetError
بالإبلاغ عن رقم الخطأ فقط ، والذي ليس من السهل فهمه حتى تحفظ أرقام الأكواد. غالبًا ما يكون من المنطقي أن تكتب وظيفة صغيرة للمساعدة في طباعة سلسلة خطأ جنبًا إلى جنب مع الموقع الذي تُسمى الوظيفة.
GLenum glCheckError_(const char *file, int line) { GLenum errorCode; while ((errorCode = glGetError()) != GL_NO_ERROR) { std::string error; switch (errorCode) { case GL_INVALID_ENUM: error = "INVALID_ENUM"; break; case GL_INVALID_VALUE: error = "INVALID_VALUE"; break; case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break; case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break; case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break; case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break; } std::cout << error << " | " << file << " (" << line << ")" << std::endl; } return errorCode; } #define glCheckError() glCheckError_(__FILE__, __LINE__)
إذا قررت إجراء المزيد من المكالمات إلى glCheckError
، فسيكون من المفيد معرفة مكان حدوث الخطأ.
glBindBuffer(GL_VERTEX_ARRAY, vbo); glCheckError();
الاستنتاج:

يبقى شيء واحد مهم: هناك خطأ طويل الأمد في GLEW: glewInit()
دائمًا بتعيين علامة GL_INVALID_ENUM
. لإصلاح ذلك ، ما glGetError
سوى الاتصال بـ glGetError
بعد glewInit
لمسح العلامة:
glewInit(); glGetError();
لا يساعد glGetError
كثيرًا ، حيث أن المعلومات التي يتم إرجاعها بسيطة نسبيًا ، لكنها تساعد غالبًا في التقاط الأخطاء المطبعية أو اكتشاف المكان الذي حدث فيه الخطأ. هذه أداة تصحيح بسيطة وفعالة.
إخراج التصحيح
الأداة أقل شهرة ، ولكنها أكثر فائدة من glCheckError
، ملحق OpenGL "إخراج التصحيح" المتضمن في OpenGL 4.3 Core Profile. بهذا الامتداد ، سترسل OpenGL رسالة خطأ إلى المستخدم مع تفاصيل الخطأ. لا يوفر هذا الملحق مزيدًا من المعلومات فحسب ، بل يتيح لك أيضًا اكتشاف الأخطاء عند حدوثها باستخدام مصحح الأخطاء.
يتم تضمين إخراج Debug في OpenGL بدءًا من الإصدار 4.3 ، مما يعني أنك ستجد هذه الوظيفة على أي جهاز يدعم OpenGL 4.3 أو أعلى. إذا لم يكن هذا الإصدار متاحًا ، فيمكنك التحقق من الامتدادات ARB_debug_output
و AMD_debug_output
. هناك أيضًا معلومات لم يتم التحقق منها مفادها أن إخراج تصحيح الأخطاء غير معتمد على OS X (لم يختبر مؤلف الأصل ولم يترجم المترجم ، يرجى إبلاغ المؤلف بالأصل أو لي في رسائل خاصة من خلال آلية تصحيح الخطأ ، إذا وجدت تأكيدًا أو دحضًا لهذه الحقيقة ؛ UPD: Jeka178RUS تم التحقق من ذلك حقيقة: من خارج منطقة الجزاء ، لا يعمل إخراج التصحيح ، وقال انه لم تحقق من خلال الملحقات).
لبدء استخدام إخراج التصحيح ، نحتاج إلى طلب سياق تصحيح OpenGL أثناء عملية التهيئة. تختلف هذه العملية في أنظمة النوافذ المختلفة ، ولكن هنا سنناقش GLFW فقط ، ولكن في نهاية المقالة في قسم "المواد الإضافية" ، يمكنك العثور على معلومات حول أنظمة النوافذ الأخرى.
إخراج التصحيح في GLFW
يعد طلب سياقات تصحيح الأخطاء في GLFW بسيطًا بشكل مدهش: كل ما عليك القيام به هو إعطاء GLFW تلميحًا إلى أننا نريد سياقًا يدعم إخراج التصحيح. نحتاج إلى القيام بذلك قبل الاتصال بـ glfwCreateWindow
:
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
بمجرد تهيئة GLFW ، يجب أن يكون لدينا سياق تصحيح إذا استخدمنا OpenGL 4.3 أو أعلى ، وإلا فإننا نحتاج إلى تجربة حظنا ونتمنى أن يظل النظام قادرًا على إنشاء سياق تصحيح. في حالة الفشل ، نحتاج إلى طلب إخراج التصحيح من خلال آلية تمديد OpenGL.
يمكن أن يكون سياق تصحيح أخطاء OpenGL أبطأ من المعتاد ، لذلك يجب عليك إزالة هذا الخط أو التعليق عليه أثناء العمل على التحسينات أو قبل الإصدار.
للتحقق من نتيجة تهيئة سياق التصحيح ، يكفي تنفيذ التعليمات البرمجية التالية:
GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
كيف يعمل إخراج التصحيح؟ نقوم بتمرير وظيفة رد الاتصال إلى معالج الرسائل في OpenGL (على غرار عمليات رد الاتصال في GLFW) وفي هذه الوظيفة يمكننا معالجة بيانات OpenGL كما نريد ، وفي حالتنا ، نرسل رسائل خطأ مفيدة إلى وحدة التحكم. النموذج الأولي لهذه الوظيفة:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, void *userParam);
لاحظ أنه في بعض أنظمة التشغيل ، قد يكون نوع المعلمة الأخيرة const void*
.
نظرًا لمجموعة البيانات الكبيرة التي لدينا ، يمكننا إنشاء أداة مفيدة لطباعة الأخطاء ، كما هو موضح أدناه:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, void *userParam) {
عندما يكتشف الامتداد خطأ في OpenGL ، فسوف يستدعي هذه الوظيفة ويمكننا طباعة كمية كبيرة من معلومات الخطأ. لاحظ أننا تجاهلنا بعض الأخطاء ، نظرًا لأنها غير مجدية (على سبيل المثال ، يشير 131185 في برامج تشغيل NVidia إلى أنه تم إنشاء المخزن المؤقت بنجاح).
الآن وبعد أن أصبح لدينا رد الاتصال المطلوب ، حان الوقت لتهيئة إخراج التصحيح:
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) { glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(glDebugOutput, nullptr); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); }
لذلك نحن نقول لبرنامج OpenGL أننا نريد تمكين إخراج التصحيح. glEnable(GL_DEBUG_SYNCRHONOUS)
الدعوة إلى glEnable(GL_DEBUG_SYNCRHONOUS)
برنامج OpenGL أننا نريد رسالة خطأ عندما حدث ذلك للتو.
تصحيح إخراج الإخراج
باستخدام وظيفة glDebugMessageControl
يمكنك تحديد أنواع الأخطاء التي تريد تلقيها. في حالتنا ، نحصل على جميع أنواع الأخطاء. إذا أردنا فقط أخطاء OpenGL API ، مثل Error ومستوى الأهمية High ، فسوف نكتب الكود التالي:
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE);
مع هذا التكوين وسياق التصحيح ، فإن كل أمر OpenGL غير صحيح سيرسل الكثير من المعلومات المفيدة:

ابحث عن مصدر الخطأ من خلال مكدس الاستدعاءات
خدعة أخرى مع إخراج التصحيح هي أنه يمكنك بسهولة نسبيا تحديد موقع الخطأ بالضبط في التعليمات البرمجية الخاصة بك. عن طريق تعيين نقطة توقف في وظيفة DebugOutput
على نوع الخطأ المرغوب فيه (أو في بداية الوظيفة إذا كنت ترغب في التقاط جميع الأخطاء) ، فسوف يصحح المصحح الخطأ ويمكنك التنقل في مكدس الاستدعاء لمعرفة مكان حدوث الخطأ:

يتطلب هذا بعض التدخل اليدوي ، ولكن إذا كنت تعرف ما تبحث عنه تقريبًا ، فمن المفيد بشكل لا يصدق تحديد المكالمة التي تسببت في الخطأ.
الأخطاء الخاصة
مع أخطاء القراءة ، يمكننا إرسالها إلى نظام إخراج التصحيح باستخدام glDebugMessageInsert
:
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM, -1, "error message here");
يعد هذا مفيدًا للغاية إذا كنت تتصل بتطبيق آخر أو رمز OpenGL يستخدم سياق تصحيح الأخطاء. سيكون بمقدور المطورين الآخرين اكتشاف أي خطأ تم الإبلاغ عنه يحدث في رمز OpenGL المخصص.
بشكل عام ، يعد إخراج التصحيح (إن وجد) مفيدًا جدًا في اكتشاف الأخطاء بسرعة وهو بالتأكيد يستحق الجهد المبذول في التوليف ، لأنه يوفر وقتًا كبيرًا من التطوير. يمكنك العثور على نسخة من التعليمات البرمجية المصدر هنا باستخدام glGetError
وتصحيح الأخطاء. هناك أخطاء ، حاول إصلاحها.
إخراج شادر التصحيح
عندما يتعلق الأمر بـ GLSL ، فليس لدينا إمكانية الوصول إلى وظائف مثل glGetError
أو القدرة على السير عبر الكود في خطوات في مصحح الأخطاء. عندما تواجه شاشة سوداء أو شاشة غير صحيحة تمامًا ، قد يكون من الصعب للغاية فهم ما يحدث إذا كانت المشكلة في الظل. نعم ، تبلغ أخطاء الترجمة عن أخطاء في بناء الجملة ، لكن اكتشاف الأخطاء الدلالية هو تلك الأغنية.
إحدى الطرق الشائعة الاستخدام لمعرفة الخطأ في تظليل هي إرسال جميع المتغيرات ذات الصلة في برنامج تظليل مباشرة إلى قناة الإخراج من تظليل التجزئة. من خلال إخراج متغيرات التظليل مباشرة إلى قناة الإخراج بلون ، يمكننا العثور على معلومات مثيرة للاهتمام عن طريق التحقق من الصورة عند الإخراج. على سبيل المثال ، نحتاج إلى معرفة ما إذا كانت القواعد الطبيعية صحيحة للنموذج أم لا. يمكننا إرسالها (المحولة أم لا) من الرأس إلى تظليل الشظايا ، حيث نشتق الأشياء الطبيعية مثل هذا:
(ملاحظة الخط: لماذا لا يوجد تسليط الضوء على بناء جملة GLSL؟)
#version 330 core out vec4 FragColor; in vec3 Normal; [...] void main() { [...] FragColor.rgb = Normal; FragColor.a = 1.0f; }
من خلال إخراج متغير غير لون لقناة الإخراج مع اللون كما هو الآن ، يمكننا التحقق بسرعة من قيمة المتغير. على سبيل المثال ، إذا كانت النتيجة شاشة سوداء ، فمن الواضح أن الحالات الطبيعية يتم نقلها بشكل غير صحيح إلى التظليل ، وعندما يتم عرضها ، يكون من السهل نسبيًا التحقق منها للتأكد من صحتها:

من النتائج المرئية ، يمكننا أن نرى أن القواعد الطبيعية صحيحة ، حيث أن الجانب الأيمن من الدعوى أحمر في الغالب (مما يعني أن الحالات الطبيعية تظهر تقريبًا في اتجاه محور الشطف x) وكذلك الجانب الأمامي من الدعوى ملون في اتجاه المحور z الموجب (الأزرق).
يمكن توسيع هذا النهج ليشمل أي متغير تريد اختباره. في كل مرة تتعثر فيها وتفترض أن الخطأ يقع في التظليلات ، حاول رسم بعض المتغيرات أو النتائج الوسيطة واكتشف أي جزء من الخوارزمية يوجد خطأ.
برنامج OpenGL GLSL مترجم مرجعي
كل برنامج تشغيل فيديو لديه المراوغات الخاصة به. على سبيل المثال ، تعمل برامج تشغيل NVIDIA على تخفيف متطلبات المواصفات قليلاً ، وتلبية برامج تشغيل AMD المواصفات بشكل أفضل (وهو ما يبدو أفضل بالنسبة لي). المشكلة هي أن تظليل يعمل على جهاز واحد قد لا كسب المال على آخر بسبب الاختلافات في برامج التشغيل.
لعدة سنوات من الخبرة ، يمكنك معرفة جميع الاختلافات بين وحدات معالجة الرسومات المختلفة ، ولكن إذا كنت تريد أن تتأكد من أن مظلات تظليلك ستعمل في كل مكان ، فيمكنك التحقق من الكود باستخدام المواصفات الرسمية باستخدام برنامج التحويل البرمجي المرجعي GLSL . يمكنك تنزيل أداة التحقق من صحة GLSL لانج من هنا ( المصدر ).
باستخدام هذا البرنامج ، يمكنك اختبار تظليلك بتمريره كوسيطة أولى إلى البرنامج. تذكر أن البرنامج يحدد نوع التظليل بالامتداد:
.vert
: تظليل قمة الرأس.frag
التجزئة: شظية شظية.geom
: تظليل هندسي.tesc
: .tesc
بالفسيفساء السيطرة على تظليل.tese
: .tese
بالفسيفساء الحوسبة شادر.comp
: حساب تظليل
تشغيل البرنامج سهل:
glslangValidator shader.vert
لاحظ أنه في حالة عدم وجود أخطاء ، فلن يقوم البرنامج بإخراج أي شيء. على تظليل قمة الرأس المكسور ، سيبدو الإخراج كما يلي:

لن يُظهر البرنامج الاختلافات بين المجمعين GLSL من AMD أو NVidia أو Intel ، ولا يمكنه حتى الإبلاغ عن جميع الأخطاء في التظليل ، لكنه على الأقل يتحقق من التظليل للتأكد من الالتزام بالمعايير.
الناتج العازلة الإطار
هناك طريقة أخرى لمجموعة الأدوات الخاصة بك وهي عرض محتويات المخزن المؤقت للإطار في جزء معين من الشاشة. على الأرجح ، كنت تستخدم غالبًا عوائق الإطارات ، وبما أن كل السحر يحدث خلف الكواليس ، فقد يكون من الصعب تحديد ما يحدث. إخراج محتويات المخزن المؤقت الإطار هو خدعة مفيدة للتحقق من صحة الأشياء.
لاحظ أن محتويات المخزن المؤقت للإطار ، كما هو موضح هنا ، تعمل مع مواد ، وليس مع كائنات في مخازن الرسم المؤقتة
باستخدام تظليل بسيط يرسم نسيجًا واحدًا ، يمكننا كتابة وظيفة صغيرة ترسم بسرعة أي نسيج في الركن الأيمن العلوي من الشاشة:
يمنحك هذا نافذة صغيرة في زاوية الشاشة لتصحيح إخراج المخزن المؤقت للإطار. من المفيد ، على سبيل المثال ، عند محاولة التحقق من صحة الحالات الطبيعية:

يمكنك أيضًا توسيع هذه الوظيفة بحيث تعرض أكثر من نسيج واحد. هذه طريقة سريعة للحصول على تعليقات مستمرة من أي شيء في المخازن المؤقتة للإطار.
برامج المصحح الخارجي
عندما يفشل كل شيء آخر ، هناك خدعة أخرى: استخدام برامج الجهات الخارجية. تم دمجها في برنامج تشغيل OpenGL ويمكنها اعتراض جميع مكالمات OpenGL لتعطيك الكثير من البيانات المثيرة للاهتمام حول تطبيقك. يمكن لهذه التطبيقات أن تحدد ملامح استخدام وظائف OpenGL ، وتبحث عن الاختناقات ، وتراقب المخازن المؤقتة للإطار والقوام والذاكرة. أثناء العمل على رمز (كبير) ، يمكن أن تصبح هذه الأدوات لا تقدر بثمن.
لقد أدرجت العديد من الأدوات الشعبية. جرب كل واحد واختر الأفضل الذي يناسبك.
RenderDoc
RenderDoc أداة تصحيح منفصلة منفصلة ( مفتوحة بالكامل). لبدء الالتقاط ، حدد الملف القابل للتنفيذ ودليل العمل. يعمل التطبيق الخاص بك كالمعتاد ، وعندما تريد مشاهدة إطار واحد ، فإنك تسمح لـ RenderDoc بالتقاط عدة إطارات للتطبيق الخاص بك. من بين الإطارات التي تم التقاطها ، يمكنك عرض حالة خط الأنابيب ، وجميع أوامر OpenGL ، وتخزين المخزن المؤقت ، والقوام المستخدمة.

CodeXL
أداة تصحيح الأخطاء CodeXL - GPU ، تعمل كتطبيق مستقل وملحق لبرنامج Visual Studio. CodeXL يوفر الكثير من المعلومات ورائع للتوصيف التطبيقات الرسومية. يعمل CodeXL أيضًا على بطاقات الرسومات من NVidia و Intel ، ولكن بدون دعم تصحيح أخطاء OpenCL.

لم أستخدم CodeXL كثيرًا ، لأن RenderDoc بدا أكثر بساطة بالنسبة لي ، لكنني قمت بتضمين CodeXL في هذه القائمة لأنها تبدو وكأنها أداة موثوقة جدًا وتم تطويرها بشكل رئيسي من قبل أحد أكبر الشركات المصنعة لوحدات معالجة الرسومات.
NVIDIA Nsight
Nsight هي أداة تصحيح أخطاء NUIDIA GPU مشهورة . ليس فقط مكونًا إضافيًا لبرنامج Visual Studio و Eclipse ، ولكنه أيضًا تطبيق منفصل . يعد البرنامج المساعد Nsight شيئًا مفيدًا جدًا لمطوري الرسوم لأنه يجمع الكثير من الإحصاءات في الوقت الفعلي فيما يتعلق باستخدام GPU وحالة كل إطار على حدة من GPU.
عند بدء تشغيل التطبيق الخاص بك من خلال Visual Studio أو Eclipse باستخدام أوامر التصحيح أو ملفات تعريف Nsight ، سيبدأ داخل التطبيق نفسه. شيء جيد في Nsight: تقديم نظام واجهة المستخدم الرسومية (واجهة المستخدم الرسومية ، واجهة المستخدم الرسومية) على رأس تطبيق قيد التشغيل يمكنك استخدامه لجمع جميع أنواع المعلومات حول التطبيق الخاص بك في الوقت الحقيقي أو تحليل الإطار حسب الإطار.

Nsight أداة مفيدة للغاية ، في رأيي ، تتجاوز الأدوات المذكورة أعلاه ، ولكن لها عيبًا خطيرًا: تعمل فقط على بطاقات رسومات NVIDIA. إذا كنت تستخدم بطاقات رسومات NVIDIA وتستخدم Visual Studio ، فمن المؤكد أن Nsight يستحق المحاولة.
, ( , VOGL APItrace ), , . , , () ( , ).
- ? — Reto Koradi.
- — Vallentin Source.
PS : - . , !