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.xhtmlI 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!