Register
Handmade Hero»Forums»Code»glBufferSubData and memory layout
76 posts
glBufferSubData and memory layout
5 months, 3 weeks ago Edited by C_Worm on May 11, 2020, 7:58 p.m. Reason: Initial post
Hey, im trying to use glbuffersubdata to update more than 1 entity (simple rectangle).

however when I change the size paramter to 8 * sizeof(Vertex) instead of 4 * sizeof(Vertex), which in my head should be 2 rectangles i don't get the result i anticipated.

i've looked in the memory window in VS, and i can see that the vertex data doesn't seem to lie tightly packed ( there are a couple of bytes between the vertices ), but even if i add much more than just 8 * sizeof(Vertex) to the glbuffersubdata size parameter, only the one rectangle shows up.


Does this have to do with the fact that the entity struct has more than just the Vertex vtx[4] data in it?

And should it therefor be better to just have the Vertex vtx[4] be standalone and not part of some bigger "Entity struct"?

All the Entities are allocated as a single big array ( Entity entity[MAX_ENTITIES] = {}; ),
so I guess they should lie behind eachother in memory and therefor I should be able to set the last glbuffersubdata parameter to the address of the vertices of the first entity, or am I wrong about that??


gamecode.h
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
struct Entity
{
    V3f pos;
    V3f dimension;
    V4f_COL col;
    V2f texCoord;
    V2f texDimension;
    
    Vertex vtx[4];
    
    void Init(float _x, float _y, float _w, float _h)
    {
        pos.x = _x;
        pos.y = _y;
        pos.z = 0.0f;
        
        dimension.x = _w;
        dimension.y = _h;
        dimension.z = 0.0f;
        
        texCoord.x = 0.0f;
        texCoord.y = 0.0f;
        
        texDimension.x = 1.0f;
        texDimension.y = 1.0f;
    }
    
    void SetSolidColor(float _r, float _g, float _b, float _a)
    {
        col.r = _r;
        col.g = _g;
        col.b = _b;
        col.a = _a;
    }
    
    void SetTextureCoordsToFullMap()
    {
        texCoord.x = 0.0f;
        texCoord.y = 0.0f;
        texDimension.x = 1.0f;
        texDimension.y = 1.0f;
    }
    
    void UpdatePos()
    {
        vtx[0].pos.x = pos.x;
        vtx[0].pos.y = pos.y;
        
        vtx[1].pos.x = pos.x;
        vtx[1].pos.y = pos.y + dimension.y;
        
        vtx[2].pos.x = pos.x + dimension.x;
        vtx[2].pos.y = pos.y;
        
        vtx[3].pos.x = pos.x + dimension.x;
        vtx[3].pos.y = pos.y + dimension.y;
    }
    
    void UpdateCol()
    {
        vtx[0].col.r = col.r;
        vtx[0].col.g = col.g;
        vtx[0].col.b = col.b;
        vtx[0].col.a = col.a;
        
        vtx[1].col.r = col.r;
        vtx[1].col.g = col.g;
        vtx[1].col.b = col.b;
        vtx[1].col.a = col.a;
        
        vtx[2].col.r = col.r;
        vtx[2].col.g = col.g;
        vtx[2].col.b = col.b;
        vtx[2].col.a = col.a;
        
        vtx[3].col.r = col.r;
        vtx[3].col.g = col.g;
        vtx[3].col.b = col.b;
        vtx[3].col.a = col.a;
    }
    
    void UpdateTexCoord()
    {
        vtx[0].texCoord.x = texCoord.x;
        vtx[0].texCoord.y = texCoord.y;
        
        vtx[1].texCoord.x = texCoord.x;
        vtx[1].texCoord.y = texCoord.y + texDimension.y;
        
        vtx[2].texCoord.x = texCoord.x + texDimension.x;
        vtx[2].texCoord.y = texCoord.y;
        
        vtx[3].texCoord.x = texCoord.x + texDimension.x;
        vtx[3].texCoord.y = texCoord.y + texDimension.y;
    }
};

#define UPDATEGAMECODE(name) extern "C" __declspec(dllexport) void name(GameState *gameState, Input *input, Entity *entity)
UPDATEGAMECODE(UpdateGameCode);
typedef void (*PFNUPDATEGAMECODE)(GameState*, Input*, Entity*);



void InitEntities(Entity *entity);


gamcode.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
UPDATEGAMECODE(UpdateGameCode)
{
    if(!gameState->initializedEntities)
    {
        InitEntities(entity);
        gameState->initializedEntities = true;
    }
    // Resest Initialization
    if(input->keyPressed['R'])
    {
        gameState->initializedEntities = false;
    }
    
    // Close program & change gamestate
    if(input->keyPressed[Esc])
    {
        gameState->running = false;
    }
    
    
    if(input->keyPressed['W'])
    {
        entity[0].pos.y -= 4.0f;
    }
    if(input->keyPressed['S'])
    {
        entity[0].pos.y += 4.0f;
    }
    if(input->keyPressed['D'])
    {
        
        entity[0].pos.x += 4.0f;
    }
    if(input->keyPressed['A'])
    {
        
        entity[0].pos.x -= 4.0f;
    }
    
    
    for(int i = 0; i < 10; i ++)
    {
        
        entity[i].UpdatePos();
        entity[i].UpdateCol();
        entity[i].UpdateTexCoord();
        
    }
    
}

void InitEntities(Entity *entity)
{
    entity[0].Init(100.0f, 200.0f, 500.0f, 200.0f);
    entity[0].SetSolidColor(1.0f, 1.0f, 1.0f, 1.0f);
    entity[0].SetTextureCoordsToFullMap();
    
    entity[1].Init(0.0f, 0.0f, 50.0f, 20.0f);
    entity[1].SetSolidColor(1.0f, 1.0f, 1.0f, 1.0f);
    entity[1].SetTextureCoordsToFullMap();
    
}


main.cpp (excerpts)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
int main()
{

    GameState gameState = {};
    Entity entity[MAX_ENTITIES] = {};
    Input input = {};
    GameCodeDLL gameCode = {};
    WindowDimension winDim = {};
    gameState.running = true;
    gameState.initializedEntities = false;
    
    u32 VAO;
    u32 VBO;
    u32 IBO;
    u32 colAttribOffset = sizeof(V3f);
    u32 texCoordAttribOffset = colAttribOffset + sizeof(V4f_COL);
    u32 MaxMemory = sizeof(float) * TOTAL_AMOUNT_ATTRIBS;
    u32 vtxIndices[MAX_ENTITIES * 6] = {};
    
    for(int i = 0; i < MAX_ENTITIES; i++)
    {
        vtxIndices[(i * 6) + 0] = (i * 4) + 0;
        vtxIndices[(i * 6) + 1] = (i * 4) + 1;
        vtxIndices[(i * 6) + 2] = (i * 4) + 2;
        vtxIndices[(i * 6) + 3] = (i * 4) + 1;
        vtxIndices[(i * 6) + 4] = (i * 4) + 2;
        vtxIndices[(i * 6) + 5] = (i * 4) + 3;
    }
    
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &IBO);
    glGenTextures(10, imageTexture);
    glGenTextures(10, fontTexture);
    
    glBindVertexArray(VAO);
    
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, MaxMemory, 0, GL_DYNAMIC_DRAW);
    
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)0);
    
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 4, GL_FLOAT, false, sizeof(Vertex), (void*)colAttribOffset);
    
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, false, sizeof(Vertex), (void*)texCoordAttribOffset);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vtxIndices), vtxIndices, GL_STATIC_DRAW);
    


while(running)
{

        // LOADING GAMECODE
        UnloadGameCode(&gameCode);
        gameCode = LoadGameCode();
        // UNLOADING GAMECODE
        
        glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        
        gameCode.UpdateGameCode(&gameState, &input, entity);
        
        glBufferSubData(GL_ARRAY_BUFFER, 0 * sizeof(entity[0].vtx), 4 * sizeof(Vertex), entity[0].vtx);

        BindTexture2D(imageTexture[myPng]);
        glDrawElements(GL_TRIANGLES, MAX_ENTITIES * 6, GL_UNSIGNED_INT, 0);
        
        BindTexture2D(imageTexture[devastator]);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (unsigned int*)(6 * 4));
        
        glfwSwapBuffers(hWindow);
        glfwPollEvents();
        
}



}
Mārtiņš Možeiko
2145 posts / 1 project
glBufferSubData and memory layout
5 months, 3 weeks ago
Uploading your vtx data with glBufferSubData will work fine regardless of where Vertex vtx[4] data is - standalone, or part of bigger struct (your Entity).

You say it does not work when you upload two vertices? How are you doing that? Are you calling glBufferSubData twice? Because vtx array's are not laid out in memory next to each other. Entity struct's are, but vtx is only part of it.
76 posts
glBufferSubData and memory layout
5 months, 2 weeks ago
mmozeiko

You say it does not work when you upload two vertices? How are you doing that? Are you calling glBufferSubData twice? Because vtx array's are not laid out in memory next to each other.



No i don't want to upload two vertices, i want to upload 4 vertices of 2 Entites (total of 8 vertices) with ONE call to glBufferSubData().

if the last param of "SubData()" is entity[0].vtx then the first entity will be updated and shown on screen. If i change it to entity[1].vtx, the second will be updated and shown on screen.
It works fine to just make TWO calls to "subData()" and change the last address to what ever entities vertices i would want to update.


but i would like them both to be updated with one call to "subData()".

However, since the vertices don't lie next to each other that will be a problem i guess, but could be solved if i took the vtx data out of the entity struct and pulled ALL the vertices in one standalone struct??

you get my point?

Even though i would want to try to solve it so that the vtx IS part of the Entity struct and not standalone...



Marc Costa
54 posts
glBufferSubData and memory layout
5 months, 2 weeks ago
As you said, it would work if you extracted the vertices of all entities into a contiguous array.

This is the memory layout of your vertices right now:

1
2
3
4
5
+--------------------------------------------------+--------------------------------------------------+
|                                                                                                                            |
| xxxxxxxxxxxxxxxxxxxxxxxx Vertex[4]  | xxxxxxxxxxxxxxxxxxxxxxxx Vertex[4]  |
|                                                                                                                            |
+--------------------------------------------------+--------------------------------------------------+


You're telling glBufferSubData to start at entity[0].vtx[0] and copy 8 vertices, but entity[1]'s vertices are not next to entity[0]'s. So you end up sending to the GPU other entity[1] data. That's also why went you point glBufferSubData to entity[1].vtx[0] it correctly uploads entity[1]'s 4 vertices.

You need an array like this to send all data in a single call:
1
Vertex allVertices[MAX_ENTITIES * 4];
76 posts
glBufferSubData and memory layout
5 months, 2 weeks ago
Yeah that's what i thought and i guess if the glBufferSubData() function had a paramter to set the offset between consecutive elements in the new data sent to be uploaded, it would work.


So i guess i should not get hung up on trying to force something to work and make it more complicated for myself :P

Marc Costa
54 posts
glBufferSubData and memory layout
5 months, 2 weeks ago
Well, you could send all Entity data to the GPU and change the data offsets in glVertexAttribPointer to account for that. Although that would be really wasteful.