-2

I am currently rendering about 10 backpack objects and a huge sponza model with OpenGL (which has about 400 meshes and 2 million triangles), and 7 point lights in my scene. The performance is ok (60fps) when the window is in 800x600 resolution, however when I make the window about the size of my screen (2560x1600), performance hugely drops to about 15-20 fps, especially when looking through the sponza model (I am using Assimp). The code structure is like the following (simplified):

Mesh.cpp:

class Mesh {
  // class that contains all vertex info (pos, normal, UV)
  void draw(const Shader& shader) const {
      // bind the textures + activate
      // bind the vao
      glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
  }
};

Model.cpp (only job is to load the meshes):

class Model {
  std::vector<Mesh> meshes;
  void draw(const Shader& shader) const {
     for (const auto& mesh: meshes) {
         mesh.draw(shader);
     }
  }
};

The thing is, since sponza model has 400 meshes, this corresponds to 400 draw calls + 10 backpack draw calls which roughly equals to 410 draw calls per frame. I guess that making a draw call for each mesh might create this performance drop. However, I am unsure how to solve it. Maybe I can put all the vertex data into one VBO and make one draw call, but I'm not exactly sure how to tackle that. Also, there are 25 materials in the sponza model.

P.S. I am using learnopengl.com's implementation of Mesh and Model, and here are the complete classes: Model.cpp

class Model {
public:
    explicit Model(const std::string &path);
    ~Model();
    void draw(const Shader &shader);

private:
    void load_model(const std::string &path);
    void process_node(aiNode *node, const aiScene *scene);
    Mesh process_mesh(aiMesh *mesh, const aiScene *scene);
    std::vector<Texture> load_material_textures(aiMaterial *mat, aiTextureType type,
                                                Texture::TextureType tex_type);

    std::vector<Mesh> meshes;
    std::vector<Texture> textures_loaded;
    std::string directory;
};

#include <string>

Model::Model(const std::string &path) {
    load_model(path);
}

Model::~Model() {
    for (const auto &mesh : meshes) {
        mesh.destroy();
    }
}

void Model::draw(const Shader &shader) {
    for (const auto &mesh : meshes)
        mesh.draw(shader);
}

void Model::load_model(const std::string &path) {
    Assimp::Importer importer;
    const aiScene *scene = importer.ReadFile(path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenSmoothNormals | aiProcess_JoinIdenticalVertices);
    if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
        std::cout << "ERROR::ASSIMP::" << importer.GetErrorString() << std::endl;
        return;
    }

    directory = path.substr(0, path.find_last_of("/"));
    std::cout << "load_model(" << path << ")" << std::endl;
    process_node(scene->mRootNode, scene);
    std::cout << meshes.size() << std::endl;
}

void Model::process_node(aiNode *node, const aiScene *scene) {
    // process each mesh located at the current node
    for (unsigned int i = 0; i < node->mNumMeshes; i++) {
        // the node object only contains indices to index the actual objects in the scene.
        // the scene contains all the data, node is just to keep stuff organized (like relations between nodes).
        aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        meshes.push_back(process_mesh(mesh, scene));
    }
    // after we've processed all of the meshes (if any) we then recursively process each of the children nodes
    for (unsigned int i = 0; i < node->mNumChildren; i++) {
        process_node(node->mChildren[i], scene);
    }
}

Mesh Model::process_mesh(aiMesh *mesh, const aiScene *scene) {
    std::vector<Vertex> vertices;
    std::vector<Texture> textures;
    std::vector<ui32> indices;

    // fill in vertex data form the model info
    for (int i = 0; i < mesh->mNumVertices; i++) {
        Vertex vertex;
        glm::vec3 temp_vec;
        // get the position data
        temp_vec.x      = mesh->mVertices[i].x;
        temp_vec.y      = mesh->mVertices[i].y;
        temp_vec.z      = mesh->mVertices[i].z;
        vertex.position = temp_vec;
        // get the normal data
        temp_vec.x     = mesh->mNormals[i].x;
        temp_vec.y     = mesh->mNormals[i].y;
        temp_vec.z     = mesh->mNormals[i].z;
        vertex.normals = temp_vec;
        // get the uv data
        if (mesh->mTextureCoords[0]) {
            glm::vec2 uv_data;
            uv_data.x = mesh->mTextureCoords[0][i].x;
            uv_data.y = mesh->mTextureCoords[0][i].y;
            vertex.uv = uv_data;
        } else
            vertex.uv = glm::vec2(0.0f, 0.0f);
        vertices.push_back(vertex);
    }
    for (int i = 0; i < mesh->mNumFaces; i++) {
        aiFace face = mesh->mFaces[i];
        for (int j = 0; j < face.mNumIndices; j++)
            indices.push_back(face.mIndices[j]);
    }

    if (mesh->mMaterialIndex >= 0) {
        aiMaterial *mat                   = scene->mMaterials[mesh->mMaterialIndex];
        std::vector<Texture> diffuse_maps = load_material_textures(mat, aiTextureType_DIFFUSE, Texture::TextureType::DIFFUSE);
        textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end());
        std::vector<Texture> specular_maps = load_material_textures(mat, aiTextureType_SPECULAR, Texture::TextureType::SPECULAR);
        textures.insert(textures.end(), specular_maps.begin(), specular_maps.end());
        std::vector<Texture> emission_maps = load_material_textures(mat, aiTextureType_EMISSIVE, Texture::TextureType::EMISSION);
        textures.insert(textures.end(), emission_maps.begin(), emission_maps.end());
    }

    return Mesh(vertices, indices, textures);
}

std::vector<Texture> Model::load_material_textures(aiMaterial *mat, aiTextureType type, Texture::TextureType tex_type) {
    std::vector<Texture> textures;
    for (int i = 0; i < mat->GetTextureCount(type); i++) {
        aiString str;
        mat->GetTexture(type, i, &str);
        bool skip = false;
        for (int j = 0; j < textures_loaded.size(); j++) {
            if (std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) {
                textures.push_back(textures_loaded[j]);
                skip = true;
                break;
            }
        }
        if (!skip) {
            Texture tex = ResourceManager::load_ogl_texture_from_path(directory + "/" + str.C_Str(), tex_type);
            tex.path    = str.C_Str();
            textures.push_back(tex);
            textures_loaded.push_back(tex);
        }
    }
    return textures;
}

Mesh.cpp:

struct Vertex {
    glm::vec3 position;
    glm::vec3 normals;
    glm::vec2 uv;
};

class Mesh {
public:
    std::vector<Vertex> vertices;
    std::vector<ui32> indices;
    std::vector<Texture> textures;

    Mesh(const std::vector<Vertex> &vertices, const std::vector<ui32> &indices, const std::vector<Texture> &textures);
    ~Mesh();
    void draw(const Shader &shader) const;
    void destroy() const;

private:
    ui32 vao, vbo, ebo;
    void setup_mesh();
};

Mesh::Mesh(const std::vector<Vertex> &vertices, const std::vector<ui32> &indices, const std::vector<Texture> &textures) : vertices(vertices), indices(indices), textures(textures) {
    setup_mesh();
}

Mesh::~Mesh() {}

void Mesh::setup_mesh() {
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glGenBuffers(1, &ebo);

    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int),
                 &indices[0], GL_STATIC_DRAW);

    // vertex positions
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
    // vertex normals
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, normals));
    // vertex texture coords
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, uv));

    glBindVertexArray(0);
}

void Mesh::draw(const Shader &shader) const {
    ui32 no_diffuse  = 1;
    ui32 no_specular = 1;
    ui32 no_emission = 1;

    for (int i = 0; i < textures.size(); i++) {
        glActiveTexture(GL_TEXTURE0 + i);
        std::string n, base_name;
        base_name = textures[i].type;
        if (std::strcmp(base_name.c_str(), "texture_diffuse") == 0)
            n = std::to_string(no_diffuse++);
        else if (std::strcmp(base_name.c_str(), "texture_specular") == 0)
            n = std::to_string(no_specular++);// transfer unsigned int to string
        else if (std::strcmp(base_name.c_str(), "texture_emission") == 0)
            n = std::to_string(no_emission++);// transfer unsigned int to string
        shader.setInt("material." + base_name + n, i);
        glBindTexture(GL_TEXTURE_2D, textures[i].id);
    }

    glBindVertexArray(vao);
    glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
    glActiveTexture(GL_TEXTURE0);
}

void Mesh::destroy() const {
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glDeleteBuffers(1, &ebo);
    for (const auto &texture : textures)
        glDeleteTextures(1, &texture.id);
}

MORE INFO: The rendering is the simple vanilla forward rendering.

genpfault
  • 51,148
  • 11
  • 85
  • 139
DarthVader
  • 37
  • 1
  • 8

1 Answers1

2

400 meshes and 2 million triangles

400 different meshes? If yes, then simplify your model, that's ridiculous. If no, then use instancing. The 400 draw calls is just as ridiculous.

however when I make the window about the size of my screen (2560x1600), performance hugely drops to about 15-20 fps

While the overall architecture of your program is near unsalvageable, that sentence indicates that at least part of the problem is that you're running into your graphics card's fill rate limit (at least ignoring the large amounts of time it's doing nothing because you're too busy looping through random objects, drawing them one by one).

Blindy
  • 65,249
  • 10
  • 91
  • 131