Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411490 Posts in 69371 Topics- by 58428 Members - Latest Member: shelton786

April 25, 2024, 12:42:28 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Idiot seeks advice regarding OpenGL VBOs and all that jazz.
Pages: [1]
Print
Author Topic: Idiot seeks advice regarding OpenGL VBOs and all that jazz.  (Read 660 times)
DrDerekDoctors
THE ARSEHAMMER
Level 8
******



View Profile WWW
« on: February 22, 2018, 10:40:05 AM »

Hullo! I'm trying to change my engine from OpenGL 1.x to lovely modern OpenGL 3.0/3.1 so that I can use all the sexy, sexy shaders. Although I'm sure it's not relevant, I'm using SDL2/C++ for the rest of it. But there's a problem - I'm not sure I entirely (or partly) understand VBOs.

Now, at the moment my engine (which I think I'm allowed to call it, because even shitty 2-stroke moped engines are still technically engines) works purely by firing tons of textured quads at the screen. There's some optimisation (i.e. when it draws the tilemap layers, it draws all the tiles from one texture before moving onto the next to avoid rebinding textures every 2 nanoseconds), but it's still a barbaric way of doing things, really.

Basically, I understand that there are things called VBOs, which are basically sausages (stop me if I'm getting too technical) into which I can stuff a load of vertices, colours and texture co-ordinates (I think...), and then I can use a shader program to render that VBO. But my question really is how to best use those sausages.

So, say I have a tilemap, which has 4 layers and is 128x128 tiles in size, where any of those tiles could be drawn from an arbitrary texture, would the sensible thing to do be to create a VBO per layer per texture?

If not, what should I be doing there?

What about all the tiles which are off-screen - would they be culled swiftly or would I have to worry about overdraw? Because you might only see 10% of that 128x128 tilemap at a time.

Also, the tilemap can obviously change on a frame-to-frame basis - is there going to be much of an overhead to re-building that VBO and squirting it to the graphics card again (assuming that I've got vertex and texture co-ord data in there, I'd assume each one could a good few hundred K in size).

Also, I suppose that I should store the tilemap VBO as a tri-strip (well, tri fan) - how do I deal with gaps in it where there's no tile? Or would I be better off making it a VBO of quads (if that's even possible) given that the likelihood of a 2 or more vertices in the same position sharing texture co-ordinates is extremely low?

And for my sprite sheets, is it possible to build a VBO which has the texture co-ordinates/pixels-size-vertices of ALL the sprites in it and then draw just a part of it? Like I'll just draw quad number 67 out of a possible 128?  Or is that not what VBOs are for at all?

Or for each sprite would I just be pushing new texture co-ord/vertex values into a pre-existing VBO of a quad and then saying "draw that!"?

As you can see, I know very little.

Thank you for any help/flaming in advance.
Logged

Me, David Williamson and Mark Foster do an Indie Games podcast. Give it a listen. And then I'll send you an apology.
http://pigignorant.com/
ThemsAllTook
Administrator
Level 10
******



View Profile WWW
« Reply #1 on: February 22, 2018, 12:01:07 PM »

Hi! Sounds like you have a fun learning journey ahead. I don't have answers to everything here, but maybe I can help fill in a few blanks:

So, say I have a tilemap, which has 4 layers and is 128x128 tiles in size, where any of those tiles could be drawn from an arbitrary texture, would the sensible thing to do be to create a VBO per layer per texture?

That sounds reasonable enough, though if it's possible, it'd be better to pack all of your tile textures together into a large atlas. If you have enough unique tile graphics at a high enough resolution, this won't be possible, of course. The usual recommendation I've heard is to avoid using the maximum texture resolution reported by the graphics driver of your minimum spec, and go one power of two down from that instead - so if you're targeting cards that have a GL_TEXTURE_MAX_SIZE of 4096, you'd use 2048 x 2048 textures and pack as much into them as you can so that you can minimize your state changes between draw calls.

What about all the tiles which are off-screen - would they be culled swiftly or would I have to worry about overdraw? Because you might only see 10% of that 128x128 tilemap at a time.

My knowledge isn't up to date on how much efficiency is gained from culling, but this should be a good place to test and profile. My suspicion is that no culling is necessary, but once you get a renderer up and running without it, you could add some cheap culling code and measure the difference to see how much it could help.

Also, the tilemap can obviously change on a frame-to-frame basis - is there going to be much of an overhead to re-building that VBO and squirting it to the graphics card again (assuming that I've got vertex and texture co-ord data in there, I'd assume each one could a good few hundred K in size).

There's some. It's always going to be most efficient to upload data to VRAM once and not change it ever, but obviously that doesn't work for all use cases. This is why the GL_DYNAMIC_DRAW usage hint for glBufferData exists - if you tag your data this way when you create the buffer, it's expected that you'll be calling glMapBuffer on it every frame and modifying its data. https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBufferData.xhtml

I have seen situations where this can backfire, though - in one case, I was getting pipeline stalls from calling glMapBuffer multiple times per frame on the same buffer. There are times when it might be better to use GL_STREAM_DRAW and call glBufferData every frame to upload new data instead of modifying it in place - especially if it might change size.

Also, I suppose that I should store the tilemap VBO as a tri-strip (well, tri fan) - how do I deal with gaps in it where there's no tile? Or would I be better off making it a VBO of quads (if that's even possible) given that the likelihood of a 2 or more vertices in the same position sharing texture co-ordinates is extremely low?

It sounds like you'd want an element array buffer here. You'd have one VBO (GL_ARRAY_BUFFER) that stores your vertex data, which would be 4 vertices per tile, and a separate one (GL_ELEMENT_ARRAY_BUFFER) that specifies indexes into that data, which would probably be 6 integers per tile for drawing with GL_TRIANGLES. I've heard of using degenerate vertices (NaN coordinates or something like that) to represent discontinuities in triangle strips, which might be worth looking into if you're really looking to squeeze the maximum amount of data into the minimum amount of storage, but it's unlikely to be necessary unless you're writing something bleeding edge. If allowing a small amount of storage inefficiency is easier to think about and doesn't cost you any measurable performance at runtime, it's probably a good idea.

And for my sprite sheets, is it possible to build a VBO which has the texture co-ordinates/pixels-size-vertices of ALL the sprites in it and then draw just a part of it? Like I'll just draw quad number 67 out of a possible 128?  Or is that not what VBOs are for at all?

Sure. You'd just modify the count and indices parameters to glDrawElements to start at a particular offset in the currently bound VBO. You'd still need to sort your vertices into contiguous blocks of the same texture, but there's no reason you couldn't store the entire map in one VBO, and draw chunks of it at a time while changing texture in between.

Hope this helps!
Logged

DrDerekDoctors
THE ARSEHAMMER
Level 8
******



View Profile WWW
« Reply #2 on: February 22, 2018, 01:29:55 PM »

That sounds reasonable enough, though if it's possible, it'd be better to pack all of your tile textures together into a large atlas. If you have enough unique tile graphics at a high enough resolution, this won't be possible, of course. The usual recommendation I've heard is to avoid using the maximum texture resolution reported by the graphics driver of your minimum spec, and go one power of two down from that instead - so if you're targeting cards that have a GL_TEXTURE_MAX_SIZE of 4096, you'd use 2048 x 2048 textures and pack as much into them as you can so that you can minimize your state changes between draw calls.

Yeah, I'm getting the feeling that atlassing everything into a single texture is the way to go - even if it's something which I only do when the game's getting closer to being done and I'm having to optimise. I suppose that even if I targetted a minimum texture size of 1024x1024 (I would assume that pretty much every graphics card in use in anything approaching a gaming PC would handle that size) that's still 4096 16x16 tiles, which I'd think was ample and then some (although I'd happily be proved wrong by my artist Smiley ).

There's some. It's always going to be most efficient to upload data to VRAM once and not change it ever, but obviously that doesn't work for all use cases. This is why the GL_DYNAMIC_DRAW usage hint for glBufferData exists - if you tag your data this way when you create the buffer, it's expected that you'll be calling glMapBuffer on it every frame and modifying its data. https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBufferData.xhtml

I have seen situations where this can backfire, though - in one case, I was getting pipeline stalls from calling glMapBuffer multiple times per frame on the same buffer. There are times when it might be better to use GL_STREAM_DRAW and call glBufferData every frame to upload new data instead of modifying it in place - especially if it might change size.

Righty, understood. And I assume I can happily index into that glBufferData data? So if it's fixed size, I could easily calculate the offsets for the verts for a tile at x,y and then, if it's destroyed, change the UVs to point at a clear part of the texture to render it "gone"?

It sounds like you'd want an element array buffer here. You'd have one VBO (GL_ARRAY_BUFFER) that stores your vertex data, which would be 4 vertices per tile, and a separate one (GL_ELEMENT_ARRAY_BUFFER) that specifies indexes into that data, which would probably be 6 integers per tile for drawing with GL_TRIANGLES. I've heard of using degenerate vertices (NaN coordinates or something like that) to represent discontinuities in triangle strips, which might be worth looking into if you're really looking to squeeze the maximum amount of data into the minimum amount of storage, but it's unlikely to be necessary unless you're writing something bleeding edge. If allowing a small amount of storage inefficiency is easier to think about and doesn't cost you any measurable performance at runtime, it's probably a good idea.

Ah, the element array buffer sounds spot-on, yeah. I'd prefer to avoid degenerate triangles because it just feels "wrong". I'd be forever looking for tiny slices of pixels. Wink

And there's nothing bleeding edge about the graphical presentation of my game. At least not yet. Smiley

Sure. You'd just modify the count and indices parameters to glDrawElements to start at a particular offset in the currently bound VBO. You'd still need to sort your vertices into contiguous blocks of the same texture, but there's no reason you couldn't store the entire map in one VBO, and draw chunks of it at a time while changing texture in between.

Hope this helps!

It does, thanks so much for taking the time!

Logged

Me, David Williamson and Mark Foster do an Indie Games podcast. Give it a listen. And then I'll send you an apology.
http://pigignorant.com/
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic