Hey guys,
I try to target some performance issues in my 2D game (OpenGL 3). I hope you can help and clearify some details, thank you very much in advance!
Until now I render each image with it's own texture, which makes a LOT of texture switching necessary, and this is probably the reason for the bad performance. Now I want to merge the images to texture atlasses. I googled a lot and did not find the best practice for rendering.
Is it the "best" way of creating a dynamic VBO at each frame? One thing I did not understand is the fact that I can only pass one render matrix to the shader. But the seperate objects, although coming from the same texture, all have different positions, rotation, scale and so on... So is it correct that I have to calculate the position of the vertices with CPU and pass all precalculated coordinates WITHOUT matrix to the shader?
I think I could ask more questions, but this is the biggest question in my head for now :) ... Maybe the rest is self-explanatory as soon as I understood this...
Thank you very much!
Regards,
floyd
Best practise: Texture atlas & VBO
I don't want to speak to best practices as I'm not really qualified to do so. Also, feel free to disregard this advice as it doesn't really answer your question. But, I struggled with texture atlases quite a bit at first as well. Whether it was this problem, or dealing with seams on the edges of textures or a number of other issues, it always felt a bit off. I later came across the concept of texture arrays. You said you're using opengl 3 and shaders, so the functionality should be available to you.
Basically, you load all of your images into a single array, and then pass the layer number to your shader at render (you can think of it like creating a 3D cube of all your textures, and you tell your shader which layer of that cube to use). Once it's set up, it incredibly simple to use and removes the cost of switching textures. You load the array once and then just pass in different layers for each object when rendering An added benefit is that you can use the array for most/all of your textures and not just the ones for the texture atlas. There are certainly some best practices I'm unaware of in their use, but for some simple games, I've used one array for all the images in the game.
Anyhow, I thought I would mention them, they saved me from much trouble. Hopefully someone will have an answer to your original question though. Again, I don't feel qualified at all to speak to best practices. In the meantime I'll try to dig up a useful tutorial for array textures.
I'm loathe to link my own implementation of them (mostly out of embarrassment, but partly because I don't want to inadvertently teach any poor coding practices), but if you're willing to ignore the rest of the code, the "LoadImage()" and "LoadAssets()" functions here are pretty straightforward. You do need to pass in the layer number to your shader and replace your normal texture with a texture array, and change your texture sampler to a sampler2DArray when rendering. But, that's about all there is to it (see here and here for shader/obejct rendering things lines 58, 75 and 78 in the Mountain.cpp file especially, and 14, 17, 27 in the shader file). Again, ignore the rest of the code as it's certainly full of bad habits and newbie ways to do things
Please note, if you try to implement this, that all of the textures must be the same size (the texture you load. you can scale however you'd like after creating the array).
Cheers.
you could reduce rendering your entire world to one quad, and use a buffer texture to depict what texture to use for each tile-sized texel, and have a shader index the buffer texture to determine which tile texture to use from the texture atlas/array. Instead of describing what textures go where via vertex, you can just use the buffer texture.
I did this, but for a 3D textured world (so I couldn't use texture arrays, which only support 2D textures) and had to manually create a 3D texture atlas.
I could give you some pointers if there's anything that stumps you along the way.
Sorry for the delay.
@Misantes: Thank you very much for the hint about texture arrays, I'll have look at it. Unfortunately my images strongly vary in size, so I don't know if it fits very well...
@radioteeth: I just read a little about buffer textures (never heard of them before :) ). Do you mean to transfer the vertex information via buffer texture to GPU? I do not really unterstand what's the difference to VBO then? I really would like to accept the offer for some pointers!
Thank you very much,
floyd
Do you mean to transfer the vertex information via buffer texture to GPU? I do not really unterstand what's the difference to VBO then? I really would like to accept the offer for some pointers!
VBO's are meant to transport information varying with each vertex. A buffer texture is meant here to transport information constant for a draw call (i.e. are the same for all vertices). Its use case is like a big uniform buffer. One difference is that a texture buffer stores values of a single format, while a UBO can store values of differing formats.
radioteeth, if I understood it correctly, has suggested to draw a fullscreen quad, e.g. specified by the vertices in a VBO. In the fragment shader there are the fragment co-ordinates available. They can be used to calculate an index into a buffer texture (some kind of integer division). From the buffer texture then a layer index (if array textures are used for the tiles) or a texel offset (if a texture atlas is used) can be fetched. This, together with a texture co-ordinate calculated from the the fragment co-ordinate using some kind of modulo arithmetic, can be used to sample the image texture. The result will then give the fragment color.
Okay... As far as I understand this leeds to quite advanced shader code, which I'm not able to do yet.
So I think I stick to the texture atlas and VBO, which seems the only solution appropriate and which I understand so far :/
Any further opinions/suggestions are welcome!
Don't used texture atlases if you have access to texture arrays. Unless you are programming OpenGL ES 2.0 you should have access to texture arrays. They do exactly what you need them to do without any hacks.
But in texture arrays the images all have to be the same size, right? My images can vary in size very much...
But in texture arrays the images all have to be the same size, right? My images can vary in size very much...
To clarify a little bit, the textures you load have to be the same size. Once loaded, you can do whatever you'd like to them.
It does take a little planning initially. Though, for the moment, you could always just take your current images and re-scale them in gimp/photoshop/etc to the size you're going to use. Don't worry if they look distorted, your uv coordinates ought to take care of things game side (I'm not 100% certain without testing myself, but they ought to still look fine and un-distorted in game).
This advice may be a little off for professional uses (i'm certain there may be some downsides to simply rescaling the images that I'm not considering) but I'm guessing for your needs you probably don't need to completely redo all of your assets.
As far as your concerns with advanced shader code, it shouldn't change them all that much, as you pretty much just change a single variable. I don't know what sort of shaders you're using currently, but I wouldn't think it requires too much (I could be wrong). If you're unfamiliar with how shaders work, it could complicate things a little for you (I'm still relatively new to shaders myself, so can commiserate with you here). But, you pretty much just need to pass in a sampler array, rather than your normal 2d sampler, and then let the shader know which index to use. I detail the changes needed for the in my original post. I don't know how different my shaders are from yours, but feel free to ask any followup questions or post your current shaders, or let us know what problems you're facing.
It took me a little while to sort this out for my own projects (I pretty much went through all this just a couple months back, so I'm certainly no expert on the matter. But, I think it gives me a perspective that might be helpful to you, as I can perhaps have an easier time relating). It seemed a lot more complicated to me than it really is in hindsight (as do most things :P)
So I'm going to try to take a crack at this and I hope this helps. Now I don't know if this is the "best way", but I do know it will work. And it does not require fancy shaders cause I can't do those and don't need them (maybe I do :$)
Is it the "best" way of creating a dynamic VBO at each frame? One thing I did not understand is the fact that I can only pass one render matrix to the shader. But the seperate objects, although coming from the same texture, all have different positions, rotation, scale and so on... So is it correct that I have to calculate the position of the vertices with CPU and pass all precalculated coordinates WITHOUT matrix to the shader?
I think I could ask more questions, but this is the biggest question in my head for now... Maybe the rest is self-explanatory as soon as I understood this...
This is what I do, I transform all vertices on the CPU. Which can easily handle rotation and scaling. You could even have a Model matrix for each of your quads to handle that stuff if you really wanted to, but I kind of think (which may not be true.. never tested it) that this would be a performance hit.Anyway, I then pass all the transformed vertices to the Shader.
Now the main part you wanted, the texture atlas are really easy, since you are going to pass in the UV cords for your quads. You can just use glBindTexture (And you only have to do this once if you can fit all your images on the atlas) for the texture atlas you need to use and then calculate where the "clipping window" should be and how far it extends.
For example: lets say you have a 64x64 texture, it has 4 images on it that are 32x32 (you can use any size you want the clipping window, explained below, just needs adjusting)
Like so:
1 | 2
-------
3 | 4
If you were going to use the whole texture, your quads would have their UV coords as:
0, 0 for the top left corner
1, 0 for the top right corner
1, 1 for the bottom right corner
0, 1 for the bottom left corner
Now if you wanted your current set of quads to only use the 4th texture, the UV coords you pass to your shader would be:
0.5, 0.5 for the top left corner
1, 0.5 for the top right corner
1, 1 for the bottom right corner
0.5, 1 for the bottom left corner
So how the hell do you get those numbers?
You just need to calculate it using the size of the texture you are binding and the size of your "clipping window"
So for texture 4 our clipping window would start at Pixel XY 32, 32 and be the size of 32x32. Where the size of the texture atlas is 64x64
UV coords 1:
startPixelX/textureWidth = 32/64 = 0.5
startPixelY/textureHeight = 32/64 = 0.5;
UV coords 2:
(startPixelX + cwWidth)/textureWidth = 64/64 = 1.0
startPixelY/textureHeight = 32/64 0.5;
UV coords 3:
(startPixelX + cwWidth)/textureWidth = 64/64 = 1.0
(startPixelY + cwHeight)/textureHeight = 64/64 = 1.0
UV coords 4:
startPixelX/textureWidth = 32/64 = 0.5
(startPixelY + cwHeight)/textureHeight = 64/64 = 0.5;
@Misantes: I understand your approach, it's definitely worth a try!
@noodleBowl: I hope using model matrices are no performance hit compared to manually rotating and scaling, because I have kind of a scene node tree which works heavily with matrices :)
Ok, now I have a lot of stuff I can try. One thing I take from here is, that obviously there seems not to be the "best" solution. I'll give texture arrays and texture atlasses a try...
Thank you very much again, you're great!
Careful with Texture Arrays on a 2D Game. Many older hardwares still don't support them and 2D games tend to be expected to run on older systems just because of the nature of 2d.
I try to keep this in mind, thanks a lot!
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.