copenglbuffervertex-buffer

OpenGL - Is it efficient to call glBufferSubData (nearly) each frame?


I have a spritesheet that contains a simple sprite animation. I can successfully extract each animation frame sprite and display it as a texture. However, I managed to do that by calling glBufferSubData to change the texture coordinates (before the game loop, at initialization). I want to play a simple sprite animation using these extracted textures, and I guess I will do it by changing the texture coordinates each frame (except if animation is triggered by user input). Anyways, this results in calling glBufferSubData almost every frame (to change texture data), and my question is that is this approach efficient? If not, how can I solve the issue? (In Reddit, I saw a comment saying that the goal must be to minimize the traffic between CPU and GPU memory in modern OpenGL, and I guess my approach violates this goal.) Thanks in advance.

For anyone interested, here is my approach:

void set_sprite_texture_from_spritesheet(Sprite* sprite, const char* path, int x_offset, int y_offset, int sprite_width, int sprite_height)
{
    float* uv_coords = get_uv_coords_from_spritesheet(path, x_offset, y_offset, sprite_width, sprite_height);
    for (int i = 0; i < 8; i++)
    {                                                   
        /* 8 means that I am changing a total of 8 texture coordinates (2 for each 4 vertices) */
        edit_vertex_data_by_index(sprite, &uv_coords[i], (i / 2) * 5 + 3 + (i % 2 != 0));
        /* 
           the last argument in this function gives the index of the desired texture coordinate 
           (5 is for stride, 3 for offset of texture coordinates in each row)
        */
    }

    free(uv_coords); 
    sprite->texture = load_texture(path); /* loads the texture -
                                             since the UV coordinate is 
                                             adjusted based on the spritesheet
                                             I am loading the entire spritesheet as
                                             a texture.
                                          */
}
void edit_vertex_data_by_index(Sprite *sprite, float *data, unsigned int start_index)
{
    glBindBuffer(GL_ARRAY_BUFFER, sprite->vbo);
    glBufferSubData(GL_ARRAY_BUFFER, start_index * sizeof(float), sizeof(data), data);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    /*
        My concern is that if I call this almost every frame, it could be not efficient, but I am not sure.
    */
}

Solution

  • Editing buffers is fine. Literally every game has buffers that change every frame. Buffers are how you get the data to the GPU so it can render it! (And uniforms. Your driver is likely to secretly put uniforms in buffers though!)

    Yes, you should minimize the amount of buffer updates. You should minimize everything, really. The less stuff the computer does, the faster it can do it! That doesn't mean you should avoid doing stuff entirely. It means you should only do as much stuff as you need to, instead of doing wasteful stuff that you don't need.

    Every time you call an OpenGL function, the driver takes some time to check how to process your request, which buffer is bound, that it's big enough, that the GPU isn't using it at the same time, etc. You want to do as few calls as possible, because that way, the driver has to check all this stuff less often.

    You are doing 8 separate glBufferSubData calls in this function. If you put the UV coordinates all next to each other in the buffer, you could update them all at once with 1 call. And if you have lots of animated sprites, you should try to put all of their UV coordinates in one big array, and update the whole array in one call - all the sprites at once.

    And loading textures from paths is really slow. Maybe your program can load 100 textures per second but that still means you blew half your frame time budget on texture loading. The texture hasn't changed anyway so why would you load it again?