OpenGL RAII البدائية على غرار

مساء الخير يا مستخدمي هبرة. لم أكتب منذ فترة طويلة وربما كان شخص ما ينتظر مقالات مني - بالطبع لا. منذ أن أصبح وقت فراغي أكثر قليلاً ، وكان GitHub فارغًا تمامًا ، قررت كتابة استنساخ Mein kampf Minecraft. مع احتمالية عالية ، سأوثق هذا - اتبع مقالاتي على habr.com . اليوم سأوضح كيف قمت بتغليف بدائل OpenGL بأسلوب RAII ، إذا كانت مثيرة للاهتمام - تحت القط.

اكتب في التعليقات ما تريد أن تقرأ عنه. هل يجب أن أكتب ماين كرافت من المقالات المؤقتة؟ الآن دعنا نبدأ. سأعرض كائن المخزن كمثال . أول شيء سنفعله هو إجراء المكالمات الرئيسية ( glGenBuffers ، glDeleteBuffers ) في فصل منفصل.

في مكان ما مثل هذا:

class buffer_object
{
public:
    buffer_object() noexcept {
        glGenBuffers(1, m_object);
    }
    
    ~buffer_object() noexcept {
        glDeleteBuffers(1, m_object);
    }
    
    uint32_t get_object() const noexcept {
        return m_object;
    }
private:
    uint32_t m_object;
};

الإيجابيات:

  • لا حاجة لرصد حذف المخزن المؤقت
  • لا يتم تمكين رأس OpenGL (إذا قمنا بنقل تعريف الوظائف إلى * .cpp)

السلبيات:

  • لا يمكننا تحديد نوع المخزن المؤقت (الصفيف ، العنصر ...) عند الربط
  • لا يمكن إنشاء العديد من المخازن المؤقتة باستدعاء واحد لـ glGenBuffers
  • مشاكل في الوصول إلى كائن بعيد إذا تم نسخ المخزن المؤقت

لحل مشكلة النسخ ، نحن ببساطة نحظرها. للقيام بذلك ، قم بإنشاء فئة غير قابلة للنسخ ورثها.

مثال على فئة غير قابلة للنسخ:

struct noncopyable
{
    noncopyable() = default;
            
    noncopyable(noncopyable&&) = default;
    noncopyable& operator = (noncopyable&&) = default;
            
    noncopyable(const noncopyable&) = delete;
    noncopyable& operator = (const noncopyable&) = delete;
};

عظيم ، ناقص مشكلة واحدة. نفكر أكثر ، كيف يمكننا حل المشكلة بالربط ... إضافة وظائف الربط / عدم الربط:

void bind(buffer_type type) noexcept {
    glBindBuffer(static_cast<GLenum>(type), m_object);
}
    
void unbind() noexcept {
    glBindBuffer(static_cast<GLenum>(type), 0);
}

buffer_type هو فئة تعداد ، ولكن يمكنك عمل شرطات خارجية (في * .hpp) ، وفي (* .cpp) تفعل شيئًا مثل:

const uint32_t array_buffer = GL_ARRAY_BUFFER;

مرة أخرى ، هذا لن يبرز

#include <GL/glew.h>

الإيجابيات:

  • لا حاجة لرصد حذف المخزن المؤقت
  • لا يتم تمكين رأس OpenGL (إذا قمنا بنقل تعريف الوظائف إلى * .cpp)
  • يمكننا الإشارة إلى نوع المخزن المؤقت (الصفيف ، العنصر ...) عند الربط
  • لا نسخ - لا توجد مشاكل في النسخ

السلبيات:

  • لا يمكن إنشاء العديد من المخازن المؤقتة باستدعاء واحد لـ glGenBuffers

لذلك ، كل شيء تقريبًا رائع ، ولكن لا يزال يتعذر علينا إنشاء العديد من المخازن المؤقتة ... لنصلحها.

يمكن أن يأخذ glGenBuffers مؤشر إلى صفيف وعدد المخازن المؤقتة المراد إنشاؤها.
نحن بحاجة إلى مصفوفة ، يمكننا استخدام std :: vector ، لكننا بحاجة فقط إلى تخصيص الذاكرة مرة واحدة فقط ، وأنا أفضل std :: array هنا ، على الرغم من أنه سيتعين علينا في المستقبل القيام بمستوى واحد آخر من التجريد بسبب هذا.

نعيد كتابة صفنا إلى std :: array ونضيف القليل من القوالب:

constexpr uint8_t default_object_count = 1;
constexpr size_t  default_index        = 0;

template <type::buffer_type type, uint32_t count = default_object_count>
class buffer_object : noncopyable
{
public:
    buffer_object() noexcept {
        glGenBuffers(count, m_object.data());
    }
    
    ~buffer_object() noexcept {
        glDeleteBuffers(count, m_object.data());
    }
    
    template <size_t index = default_index>
    void bind() noexcept
    {
        static_assert(index < count, "index larger than array size");
        glBindBuffer(static_cast<GLenum>(type), m_object[index]);
    }
    
    void unbind() noexcept {
        glBindBuffer(static_cast<GLenum>(type), 0);
    }
    
    buffer_object(buffer_object&&) = default;
    buffer_object& operator = (buffer_object&&) = default;
    
private:
    std::array<uint32_t, count> m_object;
};

وهذا ما حصلنا عليه.

الإيجابيات:

  • لا حاجة لرصد حذف المخزن المؤقت
  • يمكننا الإشارة إلى نوع المخزن المؤقت (الصفيف ، العنصر ...) عند الربط
  • لا نسخ - لا توجد مشاكل في النسخ
  • يمكننا إنشاء العديد من المخازن المؤقتة باستدعاء واحد لـ glGenBuffers

السلبيات:

  • لا يمكن ربط مخازن مختلفة على نفس الكائن
  • تمكين رأس OpenGL يبرز

حسنا ... الكثير من السلبيات ، ما الذي يمكن عمله؟

يمكنك إزالة إدراج GL / glew.h عن طريق إضافة مستوى تجريد آخر سيتم فيه استدعاء وظائف OpenGL (على أي حال ، يجب القيام بذلك إذا كان دعم OpenGL + DirectX مخططًا). يعد الربط مع المخازن المؤقتة المختلفة أكثر تعقيدًا بعض الشيء ، حيث يمكننا أن ننسى الفهرس الذي تم حظر المخزن المؤقت به ، كخيار ، أضف صفيفًا آخر وكتابة نوع المخزن المؤقت إليه. لم أفعل ذلك بعد ؛ في هذه المرحلة من التطور ، هذا يكفي بالنسبة لي.

علاوة


من أجل استخدام أكثر ملاءمة ، قمت بعمل فصل ربط نطاق. ها هو:

constexpr uint32_t default_bind_index = 0;
template <class T>
class binder : utils::noncopyable
{
public:
      template <uint32_t index = default_bind_index>
      binder(T& ref) : m_ref(ref) { m_ref.template bind<index>(); }
    ~binder()                            { m_ref.unbind();}
        
private:
    T& m_ref;
};

قد يكون البعض في حيرة من هذا الخط:

m_ref.template bind<index>();

إذا لم نحدد الكلمة الرئيسية للقالب ، فإننا نتلقى الخطأ التالي:

binder.hpp: 22: 46: كلمة رئيسية "قالب" مفقودة قبل اسم القالب "ربط"
وهذا يعني أنه في هذه الحالة ، عندما تكون T نوعًا تابعًا. لا يعرف المترجم حتى الآن ما هو نوع m_ref. نظرًا لعدم وجود ربط حتى الآن ، يقوم المترجم بمعالجته بشكل نحوي بحت ، لذلك ، يتم تفسير <على أنه عامل تشغيل أقل من. للإشارة إلى المترجم أن هذا ، في الواقع ، تخصص لقالب وظيفة عضو ، يجب عليك إضافة الكلمة الأساسية للقالب مباشرة بعد عامل النقطة.

ومثال على الاستخدام:

GLuint VAO;
    
primitive::buffer_object<type::buffer_type::array> vbo;
primitive::buffer_object<type::buffer_type::element> ebo;
    
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
    
{
    primitive::binder bind{vbo};
        
    glBufferData(GL_ARRAY_BUFFER, ...);
        
    glVertexAttribPointer(...);
    glEnableVertexAttribArray(...);
        
    glVertexAttribPointer(...);
    glEnableVertexAttribArray(...);
}
    
{
    primitive::binder bind{ebo};
        
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, ...);
        
    glBindVertexArray(0);
}

لا يستخدم هذا الرمز الكائن في Vertex Array ، لأنه لا يعمل ، لم أحسب الأسباب بعد ، لكنني سأكتشف ذلك قريبًا xD إن

أغلفة بيانات التخزين المؤقت ومكالمات OpenGL ليست جاهزة بعد ، ولا تتم إعادة كتابة المكالمات إلى DSA (الوصول المباشر للدولة) .

شكرا لأولئك الذين قرأوها حتى النهاية. سأكون سعيدًا جدًا بالنقد والتعليقات.

All Articles