Ultramodern OpenGL. الجزء 1



مرحبا بالجميع. يعلم كل شخص يعرف قليلاً عن موضوع OpenGL أن هناك عددًا كبيرًا من المقالات والدورات التدريبية حول هذا الموضوع ، لكن الكثير منها لا يؤثر على واجهة برمجة التطبيقات الحديثة ، ويتحدث بعضها عمومًا عن glBegin و glEnd . سأحاول تغطية بعض الفروق الدقيقة في واجهة برمجة التطبيقات الجديدة بدءًا من الإصدار 4. رابط إلى الجزء الثاني من المقال

هذه المرة سأحاول كتابة مقالة مفيدة وغنية بالمعلومات ، وما حدث هو أن يقرر سكان هابرا الصالحون. أرجوك سامحني على قواعدي الفقيرة (سأكون ممتنًا للتصحيحات).

إذا كنت ترغب في ذلك ، سأكتب عن تحسين OpenGL وتقليل DrawCalls.

لنبدأ!

ماذا سيكون في هذه المقالة - وظيفة OpenGL الحديثة
ما لن يكون في هذه المقالة - الأساليب الحديثة في تقديم برنامج OpenGL

المحتويات:
  • وصول الدولة المباشر
  • التصحيح
  • كائنات تظليل منفصلة
  • صفائف الملمس
  • عرض الملمس
  • عازلة واحدة لمؤشر وقمة
  • التغطية بالفسيفساء وحساب التظليل
  • تقديم المسار


DSA (الوصول المباشر للدولة)


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

كما نعلم ، فإن OpenGL هو واجهة برمجة تطبيقات بها العديد من أزرار الاختيار - glActiveTexture ، glBindTexture ، إلخ.

من هنا لدينا بعض المشاكل :

  • يمكن للاختيار والحالات الحالية إجراء تغيير أعمق في الحالة.
  • قد يكون من الضروري ربط / تغيير الوحدة النشطة لتعيين مرشح للأنسجة
  • إدارة الدولة تصبح مشكلة نتيجة لزيادة تعقيد التطبيق
  • حالة غير معروفة تؤدي إلى إعدادات إضافية
  • يمكن أن تكون محاولات حفظ / استعادة الحالة مشكلة

ماذا قدمت لنا مجموعة Khronos وكيف تساعد DSA؟

  • يضيف الوظائف التي تعمل مباشرة مع الكائن / الكائنات
  • يضبط مرشح النسيج لكائن النسيج المحدد ، وليس الحالي.
  • يربط نسيجًا بوحدة معينة ، وليس بالمادة النشطة
  • يضيف عددًا كبيرًا جدًا من الميزات الجديدة
  • يغطي الأشياء حتى OpenGL 1.x
  • يضيف ميزات اضافية.

من الناحية النظرية ، يمكن أن تساعد DSA في تقليل عدد العمليات غير المتغيرة وتغيير الحالة إلى الصفر ... ولكن هذا غير دقيق.

الآن سوف أتناول بعض الميزات الجديدة لفترة وجيزة ، ولن أتطرق إلى المعلمات بالتفصيل ، وسأترك الروابط على الويكي.

  • يستبدل glCreateTextures glGenTextures + glBindTexture (التهيئة).
    كان:
    glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); 
    أصبح:
     glCreateTextures(GL_TEXTURE_2D, 1, &name); 
  • glTextureParameterX أي ما يعادل glTexParameterX
     glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 
    الآن سنكتبها مثل هذا:
     glCreateTextures(GL_TEXTURE_2D, 1, &name); glTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP); glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTextureStorage2D(name, 1, GL_RGBA8, width, height); glTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 
  • glBindTextureUnit تحل محل glActiveTexture + glBindTexture
    إليك كيف فعلنا ذلك:
     glActiveTexture(GL_TEXTURE0 + 3); glBindTexture(GL_TEXTURE_2D, name); 
    الآن:
     glBindTextureUnit(3, name); 

أثرت التغييرات أيضًا على glTextureImage ، ولم تعد مستخدمة ، وإليكم السبب:

glTexImage غير آمن إلى حد ما ، فمن السهل جدًا الحصول على مواد غير صالحة ، لأن الوظيفة لا تتحقق من القيم عند الاتصال بها ، ويقوم برنامج التشغيل بذلك أثناء الرسم. تمت إضافة GlTexStorage لاستبدالها .

يوفر glTexStorage طريقة لإنشاء القوام مع الفحوصات التي يتم إجراؤها أثناء المكالمة ، مما يقلل من الأخطاء. يتم حل مستودع المواد بواسطة معظم ، إن لم يكن جميعها ، المشكلات التي تسببها القوام القابل للتغيير ، على الرغم من أن القوام غير الثابتة أكثر موثوقية.

أثرت التغييرات أيضًا على المخزن المؤقت للإطار:


هذه ليست جميع الميزات التي تم تغييرها. التالي في السطر هي وظائف المخازن المؤقتة:


فيما يلي قائمة بما تم تضمينه الآن في دعم DSA:

  • كائنات مجموعة قمة الرأس
  • كائنات Framebuffer
  • كائنات البرنامج
  • كائنات عازلة
  • كدسات المصفوفة
  • الكثير من الأشياء القديمة

التصحيح


منذ الإصدار 4.3 ، تمت إضافة وظائف جديدة لتصحيح الأخطاء ، في رأيي مفيدة للغاية ومريحة. الآن ستقوم OpenGL باستدعاء رد الاتصال الخاص بنا للتعرف على الأخطاء ورسائل التصحيح ، والتي يمكننا ضبط مستوىها.



نحن بحاجة إلى استدعاء وظيفتين فقط لتمكين: glEnable & glDebugMessageCallback ، في أي مكان أسهل.

 glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(message_callback, nullptr); 

الآن سوف نكتب وظيفة رد الاتصال للحصول على الرسالة:

 void callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param) { auto source_str = [source]() -> std::string { switch (source) { case GL_DEBUG_SOURCE_API: return "API"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM"; case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER"; case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY"; case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION"; case GL_DEBUG_SOURCE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto type_str = [type]() { switch (type) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto severity_str = [severity]() { switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; default: return "UNKNOWN"; } }(); std::cout << source_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message << std::endl; } 

يمكننا أيضا تكوين عامل التصفية باستخدام glDebugMessageControl . يمكن أن يعمل المرشح في وضع التصفية حسب المصدر / النوع / الأهمية أو مجموعة من الرسائل باستخدام معرفاتها.

تصفية الرسائل في نطاق معين:

 glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION, DEPTH_FILL_ID, 11, “Depth Fill”); //  Render_Depth_Only_Pass(); //  glPopDebugGroup(); //  

سيكون من المفيد للغاية تعطيل المكالمة غير المتزامنة حتى نتمكن من تحديد ترتيب استدعاءات الوظائف ، وكذلك العثور على مكان الخطأ على المكدس عند تصحيح الأخطاء. يتم ذلك بكل بساطة:

 glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 

تجدر الإشارة إلى أنه ليس من الآمن استدعاء وظائف OpenGL أو نافذة في وظيفة مكالمة ملفوفة ، كما أن عمليات الاستدعاء ليست مجانية ويجب ألا تتركها مدرجة في إصدار الإصدار.
مزيد من التفاصيل حول هذه الأشياء الصغيرة وغيرها - حيل ، يمكنك أن تقرأ هنا .

SSO (كائنات تظليل منفصلة)


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


تسمح SSOs لنا بتغيير خطوات التظليل أثناء التنقل دون إعادة ربطها. فيما يلي إنشاء خط أنابيب برنامج بسيط وإعداده دون تصحيح الأخطاء:

 GLuint pipe = GL_NONE; // Create shaders GLuint fprog = glCreateShaderProgramv( GL_FRAGMENT_SHADER, 1, &text); GLuint vprog = glCreateShaderProgramv( GL_VERTEX_SHADER, 1, &text); // Bind pipeline glGenProgramPipelines( 1, &pipe); glBindProgramPipelines( pipe); // Bind shaders glUseProgramStages( pipe, GL_FRAGMENT_SHADER_BIT, fprog); glUseProgramStages( pipe, GL_VERTEX_SHADER_BIT, vprog); 

كما نرى ، يقوم glCreateProgramPipelines بإنشاء واصف وتهيئة الكائن ، ويقوم glCreateShaderProgramv بإنشاء برنامج التظليل وتهيئته وتجميعه وربطه باستخدام المصادر المحددة ، وتربط glUseProgramStages خطوات البرنامج بكائن خط الأنابيب. glBindProgramPipeline - يربط خط أنابيب مع السياق.

ولكن هناك تحذير واحد ، والآن يجب أن تطابق معلمات الإدخال والإخراج للتظليل. يمكننا إعلان معلمات الإدخال / الإخراج بنفس الترتيب ، وبنفس الأسماء ، أو أن نجعل موقعهم يتزامن بوضوح مع مساعدة التصفيات.
أوصي بالخيار الأخير ، سيتيح لنا ذلك تكوين واجهة محددة بوضوح ، وكذلك المرونة فيما يتعلق بالأسماء والترتيب.

لتوفير واجهة أكثر صرامة ، نحتاج أيضًا إلى إعلان كتل الإدخال والإخراج المضمنة التي نريد استخدامها لكل مرحلة.

يتم تعريف واجهات الكتلة المضمنة على أنها ( من ويكي ):
قنة:

 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

التحكم بالفسيفساء:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_out[]; 

تقييم التغطية بالفسيفساء:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

الهندسة:
 out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; }; 

مثال على إعادة الإعلان عن وحدة مدمجة واستخدام موقع السمة في تظليل رأس عادي:

 #version 450 out gl_PerVertex { vec4 gl_Position; }; layout (location = 0) in vec3 position; layout (location = 1) in vec3 color; layout (location = 0) out v_out { vec3 color; } v_out; void main() { v_out.color = color; gl_Position = vec4(position, 1.0); } 

Source: https://habr.com/ru/post/ar456932/


All Articles