Opengl ECS performance problem

Started by
9 comments, last by lexington 7 years, 7 months ago

Hey everyone,

I have been building a component entity system for my game and so far it has gone pretty well but today I have hit a problem.

Having just but it a system for creating and firing bullets. when i click to fire bullets it seems that i get some weird lag (most likely performance) that makes it seem as my ship is jolting forward. After looking at the VS time while debugging, it seems that after firing the bullet it takes around 30 ms to get back to the bullet system and most of this time is being take up by the init system (taking around 25 ms setting up VAO, VBO, shaders and textures). So my question (as someone who is still learning about opengl) is how would i decrease this time for setting all of this up. I will post the relevent code for the init systems and all other classes i use below.

Thanks for all help provided in advance :)

On a node about function times, it seems as the function "com->myshade->loadshader(com->vertexshadername.c_str(), GL_VERTEX_SHADER);" with the line "GLuint Shader = glCreateShader(type);" Is taking around 10 ms to complete which im guessing is why its taking so long but i don't really know why (the fragment call finished in less then a ms).

Init system


void initsystem::update(float dt, entitymanager *mymanager, bool &go)
{

    std::vector<entity*> setupentites = mymanager->returnsetupent();

    for (int i = 0; i < setupentites.size(); i++)
    {
        BasicModelcomponent *com = static_cast<BasicModelcomponent*>(setupentites[i]->getcomponent(BasicModelcomponent::TypeID));
        texturecomp *textcomp = static_cast<texturecomp*>(setupentites[i]->getcomponent(texturecomp::TypeID));
        vectorimage *entityvec = static_cast<vectorimage*>(setupentites[i]->getcomponent(vectorimage::TypeID));
        skyboxcomp *mycomp = static_cast<skyboxcomp*>(setupentites[i]->getcomponent(skyboxcomp::TypeID));

        if (com != NULL && com->setupstate == false)
        {
            com->vetrices = com->getdata->retuvetices();
            com->normals = com->getdata->retunormals();
         com->uvs = com->getdata->retutexterco();
            com->myVAO = new VAO();
            com->modeldata = new meshd();
            com->UVdata = new meshd();
            com->myshade = new shader();
        
            com->myVAO->genbuffer(1);
            com->myVAO->bindVAO();

            com->modeldata->genbuffer();

            com->modeldata->addvec3buffer(com->vetrices);

            com->modeldata->addvec3buffer(com->normals);

            com->modeldata->bufferdatavec3(GL_STATIC_DRAW);

            com->modeldata->linkdata(0, 3, 0, 0);

            int test = sizeof(glm::vec3) * com->vetrices.size();

            com->modeldata->linkdata(1, 3, 0, (void*)test);

            test += sizeof(glm::vec3) * com->normals.size();

            com->modeldata->linkdata(3, 3, 0, (void*)test);

            com->UVdata->genbuffer();

            com->UVdata->addvec2buffer(com->uvs);

            com->UVdata->bufferdatavec2(GL_STATIC_DRAW);

            com->UVdata->linkdata(2, 2, 0, 0);

            if (textcomp != NULL)
            {
                textcomp->mytexture->loadtexture();
                textcomp->mytexture->gentexture(1);
                textcomp->mytexture->twodtexturesetup(true);
            }

            com->modeldata->unbindbuffer();
            com->UVdata->unbindbuffer();

            com->myVAO->unbindVAO();

            com->myshade->loadshader(com->vertexshadername.c_str(), GL_VERTEX_SHADER);

            com->myshade->loadshader(com->fragmentshadername.c_str(), GL_FRAGMENT_SHADER);

            com->myshade->linkshader();

            bool linkok = com->myshade->linkcheck();

            shipshaderlinks *SSLcom = static_cast<shipshaderlinks*>(setupentites[i]->getcomponent(shipshaderlinks::TypeID));
            lightcomp *lightlinks = static_cast<lightcomp*>(setupentites[i]->getcomponent(lightcomp::TypeID));
            if (SSLcom != NULL)
            {
                SSLcom->shaderModelMatLocation = glGetUniformLocation(com->myshade->returnprogram(), "modelMat");
                SSLcom->shaderViewMatLocation = glGetUniformLocation(com->myshade->returnprogram(), "viewMat");
                SSLcom->shaderProjMatLocation = glGetUniformLocation(com->myshade->returnprogram(), "projMat");
                if (textcomp != NULL)
                {
                    textcomp->texture = glGetUniformLocation(com->myshade->returnprogram(), "myTextureSampler");
                    textcomp->Gamma = glGetUniformLocation(com->myshade->returnprogram(), "gamma");
                }
                if (lightlinks != NULL)
                {
                    lightlinks->strenght = glGetUniformLocation(com->myshade->returnprogram(), "strength");
                    lightlinks->cameralocation = glGetUniformLocation(com->myshade->returnprogram(), "camerapos");
                    lightlinks->shine = glGetUniformLocation(com->myshade->returnprogram(), "shine");
                }

                com->setupstate = true;
            }

        }
Advertisement

com->myVAO = new VAO(); com->modeldata = new meshd(); com->UVdata = new meshd(); com->myshade = new shader();

If I understand well, you are creating new VAO/VBO/shaders objects each time ?

You simply should not. Create them once for all and reuse them.

@_silence_

So your saying that I should create a VAO, VBO and shader at the start for each model/entity and then just reuse them?

@_silence_

So your saying that I should create a VAO, VBO and shader at the start for each model/entity and then just reuse them?

Yes.

You will even reuse the same shaders for several models. You will even might reuse the same VAO/VBO.

Bindings are generally costly. But this will not make an application having lags. However creating and deleting might. And for sure compiling shaders.

You'll have to carefully pay attention to the data transfer from the CPU to the GPU (so to the VBO). This can be a very big pitfall.

@_Silence_

I see how you could use the same program for each model as it just holds the shaders you want but i thought that the the data in the VBO could only be used for one instance of the model (then you had to add the data for the next one even though it is the same vertex data), am i wrong and you can use it for multiple of the same model, if not how would i reuse the VAO/VBO for each bullet/ship?

You're correct that you're wrong :) you can (and should) reuse the buffers (and everything else too). In rare cases where geometry is so dynamic it has to be changed each frame you can do it, but not in a way you're doing it in your code, but rather mapped buffers and not initializing every GL resource each update call. Allocating objects in such loops each frame without any sort of pooling isn't helping either.

First thing I would suggest is getting rid of re-updating GL resources each frame.


Where are we and when are we and who are we?
How many people in how many places at how many times?

@noizex

Ok so let me see if i understand what is being said (do have couple of questions at the end of the post :))

For the shader program, i can make a class that has already pre loaded all the the shader data needed, so that when it comes to creating a new bullet or fighter, i can just call of that program to be returned and use that.

When it comes to the VAO and VBO i would also have a class that can return the VAO to the BasicModelcomponent so that when it comes to drawing, all i have to do is glBindVertexArray then draw.

Is this correct or am i missing out something.

also in terms of my question, what do u mean about re-updating the GL resources every frame, the init system will only ever run anything if there is an object to be created.

Cheers

Well, it's hard to say for us what InitSystem does in a big picture (so far we know it allocates a lot of resources) and how often the update is called. But if you're doing this loop for every created bullet object that should be very lightweight (check flyweight pattern for example) and creating separate buffers, shaders and so on - then it's already pretty bad for performance, doesn't have to happen each frame (and for bullets that are short-lived objects it may happen quite often).

Considering you have bullets and fighter objects, looking at some basic situation you should have no need to use more than 2 different shaders (like one for solid static objects and other for some billboarding or whatever technique you use to render bullets, I don't even know if this is 3D or 2D case).

You're correct about VAO, you can just bind VAO and draw all same objects with it, changing only model matrix.


Where are we and when are we and who are we?
How many people in how many places at how many times?

@[member='noizex'], Arr so instead of a loop to provide the BasicModelcomponent with what it needs, i should just ask the flyweight manager to provide back what is needed (and create a new object if needed) therefore i wont really even need the init system as everything can be setup when calling the entity manager to create the bullet. Did i leave anything out?

Sounds good. Basically you want to separate things that are resources like vertex/index buffers, textures, shaders from your components. Maybe when creating new object ask some resource cache for needed resources and provide them to components. Important thing is to not do it for every object separately but keep one loaded version of each resource and just refer to it.

This can be too much for now, but another optimisation is keeping a state cache. If you render 10 meshes and all of them use the same VAO don't set this VAO 10 times, but keep a state of currentVAO/currentShader/currentRenderState and check if you really need to make GL call. This allows to grealy save on amount of GL calls, and more so if you queue your geometry so you can sort it based on shader/material/state to achieve even less state changes.


Where are we and when are we and who are we?
How many people in how many places at how many times?

This topic is closed to new replies.

Advertisement