مساء الخير يا مستخدمي هبرة. لم أكتب منذ فترة طويلة وربما كان شخص ما ينتظر مقالات مني - بالطبع لا. منذ أن أصبح وقت فراغي أكثر قليلاً ، وكان 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 (الوصول المباشر للدولة) .شكرا لأولئك الذين قرأوها حتى النهاية. سأكون سعيدًا جدًا بالنقد والتعليقات.