2013-06-18 40 views
12

Tôi muốn viết một trình bao bọc RAII đơn giản cho các đối tượng OpenGL (kết cấu, bộ đệm khung hình, v.v.) Tôi đã nhận thấy rằng tất cả các hàm glGen*glDelete* đều có cùng chữ ký, vì vậy lần thử đầu tiên của tôi giống như thế này :RAII wrapper cho các đối tượng OpenGL

typedef void (__stdcall *GLGenFunction)(GLsizei, GLuint *); 
typedef void (__stdcall *GLDelFunction)(GLsizei, const GLuint *); 

template <GLGenFunction glGenFunction, GLDelFunction glDelFunction> 
class GLObject 
{ 
    GLuint m_name; 
public: 
    GLObject() 
    { 
     glGenFunction(1, &m_name); 
    } 

    ~GLObject() 
    { 
     glDelFunction(1, &m_name); 
    } 

    GLuint getName() {return m_name;} 
}; 

typedef GLObject<glGenTextures, glDeleteTextures> GLTexture; 

Nó hoạt động tốt cho kết cấu, nhưng không cho bộ đệm khung: glGenFramebuffersglDeleteFramebuffers địa chỉ chức năng không được biết đến tại thời gian biên dịch, và không thể được sử dụng như các đối số mẫu. Vì vậy, tôi đã thực hiện phiên bản thứ hai:

class GLObjectBase 
{ 
    GLuint m_name; 
    GLDelFunction m_delFunction; 

public: 
    GLObjectBase(GLGenFunction genFunc, GLDelFunction delFunction) 
     : m_delFunction(delFunction) 
    { 
     genFunc(1, &m_name); 
    } 

    GLuint getName() 
    { 
     return m_name; 
    } 

protected: 
    ~GLObjectBase() 
    { 
     m_delFunction(1, &m_name); 
    } 
}; 

class GLFrameBuffer : public GLObjectBase 
{ 
public: 
    GLFrameBuffer() : GLObjectBase(glGenFramebuffers, glDeleteFramebuffers) {} 
}; 

Nhưng tôi không thích vì tôi phải lưu trữ con trỏ hàm del trong mỗi trường hợp sẽ không thay đổi trong thời gian chạy.

Làm cách nào để tạo lớp trình bao bọc chỉ lưu trữ tên đối tượng trong mỗi trường hợp mà không cần phải tạo một loạt các lớp được sao chép gần như sao chép?

tôi có thể làm một cái gì đó như thế này:

template <int N> 
class GLObject2 
{ 
    GLuint m_name; 
    static GLDelFunction glDelFunction; 
public: 
    GLObject2(GLGenFunction genFunction, GLDelFunction delFunc) 
    { 
     genFunction(1, &m_name); 
     if (glDelFunction == nullptr) 
      glDelFunction = delFunc; 
     ASSERT(glDelFunction == delFunc); 
    } 

    GLuint getName() {return m_name;} 

protected: 
    ~GLObject2() 
    { 
     glDelFunction(1, &m_name); 
    } 
}; 

template <int N> 
GLDelFunction GLObject2<N>::glDelFunction = nullptr; 

class GLTexture: public GLObject2<1> 
{ 
public: 
    GLTexture(): GLObject2<1>(glGenTextures, glDeleteTextures) {} 
}; 

class GLRenderBuffer: public GLObject2<2> 
{ 
public: 
    GLRenderBuffer(): GLObject2<2>(glGenRenderbuffers, glDeleteRenderbuffers) {} 
}; 

bất cứ ai có thể đề xuất giải pháp thanh lịch hơn?

+0

IMHO, API OpenGL có lẽ không hoàn toàn hợp nhất cho loại tiếp cận chung này rất thành công. Các thế hệ tính năng khác nhau đã sử dụng các phương pháp thiết kế API khác nhau. – Trillian

+3

@Trillian: Vô nghĩa. Trong số 12 loại đối tượng OpenGL, [chỉ có 3 sử dụng methadologies không phá hủy/tạo tiêu chuẩn] (http://www.opengl.org/wiki/OpenGL_Object#Non-standard_objects). Đây là một ý tưởng tồi vì những lý do khác, nhưng không phải vì lý do đó. –

+1

@NicolBolas: Hết sức tò mò: Điều gì khiến ý tưởng này trở nên tồi tệ? Và bạn có trả lời do đó một phần của quyết định xấu này hay bạn sẽ giới thiệu nó? – Skalli

Trả lời

15

Thực sự, bạn đang nghĩ về điều này giống như một lập trình viên C. Bạn đang sử dụng C++, do đó, giải quyết nó theo cách lập trình C++. Với một đặc điểm lớp học:

struct VertexArrayObjectTraits 
{ 
    typedef GLuint value_type; 
    static value_type Create(); 
    static void Destroy(value_type); 
}; 

Giống như một lớp đặc điểm C++ thích hợp, mỗi đối tượng khai báo riêng là value_type. Điều này sẽ cho phép bạn thích ứng với các đối tượng OpenGL không sử dụng GLuint s, như sync objects (mặc dù giao diện Tạo/Phá hủy sẽ không tốt cho chúng, vì vậy bạn có thể không nên bận tâm).

Vì vậy, bạn viết một loại đặc điểm cho từng loại đối tượng OpenGL. Các chức năng CreateDestroy của bạn sẽ chuyển tiếp các cuộc gọi đến C API một cách thích hợp.

Sau khi làm điều đó, tất cả bạn cần là một RAII-wrapper xung quanh những giao diện:

template<typename T> 
class OpenGLObject 
{ 
public: 
    OpenGLObject() : m_obj(T::Create()) {} 
    ~OpenGLObject() {T::Destroy(m_obj);} 

    operator typename T::value_type() {return m_obj;} 

private: 
    typename T::value_type m_obj; 
}; 

Một OpenGLObject<VertexArrayObjectTraits> sẽ tổ chức một VAO.

+4

Tôi vội vàng đánh dấu câu trả lời này là được chấp nhận, nhưng sau khi cho nó một số suy nghĩ tôi không thích nó rất nhiều. Tôi có thể đã tạo ra một nửa tá các lớp được sao chép sẽ chỉ khác nhau trong các chức năng được sử dụng để xây dựng và phá hủy các đối tượng. Đề xuất của bạn trong khi được [hơi] tốt hơn về mặt kiến ​​trúc vẫn để lại cho tôi với cùng một lượng bản sao được dán ngay bây giờ các đặc điểm-lớp học cộng với lớp đối tượng. – n0rd

+3

@ n0rd: Cùng một số lớp học có, nhưng các lớp học là 5 dòng EASY, ngoại trừ một lớp vẫn tương đối đơn giản và 20 dòng. Đó vẫn là một chiến thắng lớn trong sự phức tạp. –

+0

Việc triển khai Naïve sẽ không lớn hơn nhiều hay phức tạp: biến đổi để giữ tên đối tượng, hàm tạo một dòng, phá hủy một dòng, getter một dòng. So sánh với các đặc điểm lớp: 'value_type' typedef,' Create' function, 'Destroy'. – n0rd

9

Tại sao lại phát minh bánh xe? Có một giải pháp gọn gàng sử dụng std::unique_ptr, mà đã cung cấp các chức năng cần thiết, vì vậy bạn cần phải chỉ viết những đặc điểm (!):

template<void (*func)(GLuint)> 
struct gl_object_deleter { 
    struct pointer { // I wish we could inherit from GLuint... 
     GLuint x; 
     pointer(std::nullptr_t = nullptr) : x(0) {} 
     pointer(GLuint x) : x(x) {} 
     operator GLuint() const { return x; } 
     friend bool operator == (pointer x, pointer y) { return x.x == y.x; } 
     friend bool operator != (pointer x, pointer y) { return x.x != y.x; } 
    }; 
    void operator()(GLuint p) const { func(p); } 
}; 

void delete_texture(GLuint p) { glDeleteTextures(1, &p); } 
void delete_shader(GLuint p) { glDeleteShader(p); } 
// ... 
typedef std::unique_ptr<void, gl_object_deleter<delete_texture>> GLtexture; 
typedef std::unique_ptr<void, gl_object_deleter<delete_shader>> GLshader; 
// ... 

Hầu hết các Create* chức năng trả về một mảng thông qua lập luận của họ, mà là bất tiện khi bạn phân bổ từng đối tượng của bạn. Có thể định nghĩa một tập hợp các thói quen tạo cho trường hợp duy nhất:

GLuint glCreateTextureSN(GLenum target) { GLuint ret; glCreateTextures(target, 1, &ret); return ret; } 
GLuint glCreateBufferSN() { GLuint ret; glCreateBuffers(1, &ret); return ret; } 
// ... 

Một số chức năng OpenGL, như glCreateShader có thể được sử dụng trực tiếp. Bây giờ chúng ta có thể sử dụng nó như sau:

GLtexture tex(glCreateTextureSN(GL_TEXTURE_2D)); 
glTextureParameteri(tex.get(), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 
// ... 
GLtexture tex2 = std::move(tex); // we can move 
tex2.reset(); // delete 
return tex2; // return... 

Một nhược điểm là bạn không thể xác định một diễn viên tiềm ẩn để GLuint, vì vậy bạn phải gọi get() một cách rõ ràng.Nhưng, trên một ý nghĩ thứ hai, ngăn chặn một tình cờ cast GLuint không phải là một điều xấu.

+2

Các đối tượng giống như OpenGL cũng là một trong những ví dụ cổ điển khi sử dụng shared_ptr thực sự tốt. Kết cấu và nội dung thường được chia sẻ giữa các vị trí khác nhau trong trình kết xuất của bạn và chỉ xóa nó khi không ai cần nó nữa chính xác là những gì shared_ptr làm. – TravisG

+0

@TravisG: Một vấn đề của 'shared_ptr' so với' unique_ptr' là hàm 'get()' của nó luôn trả về một con trỏ. Bạn kết thúc việc phân bổ 'GLuint' trên heap, hoặc viết một' reinterpret_cast rõ ràng (tex.get()) '. Có lẽ bạn có thể viết một wrapper của một số loại mặc dù. – ybungalobill

+1

Bạn có thể xem bài đăng của JohannesD [từ chủ đề này] (http://stackoverflow.com/a/6272139). Kiểu GLuint có thể không đáp ứng yêu cầu NullablePointer của kiểu lưu trữ tùy chỉnh unique_ptr. – Bovinedragon

Các vấn đề liên quan