1
\$\begingroup\$

I want to create a class which is a "Renderable class". Basically, world objects will use this as a Base class to create objects that can be drawn into an OpenGL window. This is what i 've got so far:

typedef struct {
    GLfloat pos_x_, pos_y_, pos_z_;
    std::string object_path_;
    std::string texture_path_;
} OpenGLObjectParams_t;

class OpenGLObject {
public:
    OpenGLObject(OpenGLObjectParams_t params);

    int Init(OpenGLContext * context_);

    int Destroy();

    int Draw();

    void SetPosition(GLfloat pos_x, GLfloat pos_y, GLfloat pos_z);

private:
    bool is_inited_;
    glm::mat4 model_;
    OpenGLObjectParams_t params_;
    OpenGLShaderVariables_t shader_vars_;

    GLuint VAO, vertex_buffer_, color_buffer_;
};

And the implementation (Right now the object/texture are not used, and a rectangle is painted instead). The OpenGLContext is just used to take the shader variables:

OpenGLObject::OpenGLObject(OpenGLObjectParams_t params) {
        params_ = params;

        is_inited_ = false;
    }

    int OpenGLObject::Init(OpenGLContext * context) 
    {
        if (is_inited_) return -1;

        SetPosition(params_.pos_x_, params_.pos_y_, params_.pos_z_);

        shader_vars_ = context->GetShaderVariables();

        static const GLfloat vertex_buffer_data[] = {
            -1.0f, 1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
            1.0f, 1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
            1.0f, -1.0f, 0.0f,
            1.0f, 1.0f, 0.0f,
        };
        glGenBuffers(1, &vertex_buffer_);
        glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_buffer_data), vertex_buffer_data, GL_STATIC_DRAW);

        static const GLfloat color_buffer_data[] = {
            1.0f,  0.0f,  0.0f,
            0.0f,  0.0f,  0.0f,
            1.0f,  1.0f,  1.0f,
            0.0f,  0.0f,  0.0f,
            1.0f,  0.0f,  0.0f,
            1.0f,  1.0f,  1.0f,
        };
        glGenBuffers(1, &color_buffer_);
        glBindBuffer(GL_ARRAY_BUFFER, color_buffer_);
        glBufferData(GL_ARRAY_BUFFER, sizeof(color_buffer_data), color_buffer_data, GL_STATIC_DRAW);

        /* Configure a VAO */
        glGenVertexArrays(1, &VAO);
        glBindVertexArray(VAO);
        /* Attribute number 0 is the model vertices */
        glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
        glVertexAttribPointer(shader_vars_.attr_vertex_position_, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
        glEnableVertexAttribArray(0);
        /* Attribute number 1 is the colors */
        glBindBuffer(GL_ARRAY_BUFFER, color_buffer_);
        glVertexAttribPointer(shader_vars_.attr_vertex_color_, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
        glEnableVertexAttribArray(1);


        is_inited_ = true;
        return 0;
    }

    int OpenGLObject::Destroy() {
        if (!is_inited_) return -1;

        glDeleteBuffers(1, &vertex_buffer_);
        glDeleteBuffers(1, &color_buffer_);
        glDeleteVertexArrays(1, &VAO);

        is_inited_ = false;
        return 0;
    }

    int OpenGLObject::Draw(double delta_time) {
        if (!is_inited_) return -1;

        glBindVertexArray(VAO);
        glUniformMatrix4fv(shader_vars_.uni_Model_, 1, GL_FALSE, &model_[0][0]);

        glDrawArrays(GL_TRIANGLES, 0, 2 * 3);

        return 0;
    }

    void OpenGLObject::SetPosition(GLfloat pos_x, GLfloat pos_y, GLfloat pos_z) {
        params_.pos_x_ = pos_x;
        params_.pos_y_ = pos_y;
        params_.pos_z_ = pos_z;
        model_ = GetTranslateMatrix(glm::vec3(pos_x, pos_y, pos_z));
    }

And the main loop will call the Draw function on the objects that use the above as a Base class.

Is this the way to implement such a mechanism?

Because if the world has thousands of objects, then there will be a loop on a data structure to call thousands of Draw calls. I assume that the Draw() above cannot be called from multiple threads?

What's the best architecture for such a feature? Is it possible to call a Draw() from different threads?

Edit: I don't ask about the drawing method itself, since it's better to use glDrawElements() but the whole architecture.

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Is this the way to implement such a mechanism?

I would argue that no, it's not. It will work -- games have certainly shipped using a similar approach -- and if your project is simple enough and the set of "drawable subclasses" you expect to implement is small, it will be fine. But in my experience, it tends to scale poorly, introducing a lot of maintenance overhead and forcing you to contort your code to adapt to rendering techniques that require different information than your base class supports. See also, "Should actors in a game be responsible for drawing themselves?"

I assume that the Draw() above cannot be called from multiple threads?

The way you've implemented it, no. Generally draw submission needs to be synchronized onto a single thread, if you want to multithread "drawing," you tend to instead do the construction of a command buffer necessary to draw on various threads (without actually touching the underlying graphics API), and synchronize access to that onto the render thread when all the buffers have been built up and can simply be submitted.

What's the best architecture for such a feature?

I'd advise you to invert the relationship: the renderer itself knows how to draw (as in, how to translate to OpenGL) a variety of basic primitives (static meshes, animated meshes, 2D sprites, debug lines, et cetera -- broad categories). Your game objects themselves hold an instance of some "drawing description" that says how that object should be presented.

This lets you parallelize the setup of those description objects: since they are just independent chunks of data, you can do any per-frame reflection of game state into the draw description on multiple threads if you like.

Perhaps most importantly, when you then loop over all your objects and enqueuer their render descriptions in the renderer, the renderer can rearrange the submission order to batch the objects most efficiently. For example you generally want to avoid state switches, so the ideal way to render objects would be "everything that uses this render state" or "everything that uses these textures" together, or similar. By moving the actual rendering of objects out of the objects themselves, you don't need to mandate that those objects themselves be sorted in optimal render order. You just let the renderer reorganize them on submission. This frees you up to organize the actual game objects in a fashion that is more optimal for their other, game-logic-related needs.

It also centralizes the majority of your render state management in one place, making it easier to debug and eventually (if you like) to port to a different rendering back end.

\$\endgroup\$
2
  • \$\begingroup\$ Also in my way assets are multiple times in the memory, right? \$\endgroup\$
    – k_kaz
    Dec 16, 2017 at 18:58
  • \$\begingroup\$ Looks like it; you could fix that though without changing too much else about the system. \$\endgroup\$
    – user1430
    Dec 16, 2017 at 23:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .