Search code examples
xnamonogametexture2dtexture-atlas

Repeating portion of a Texture2D in a single SpriteBatch.Draw call


I have a large Texture2D with many sprites and I want to draw one of these sprites repeatedly. I'm currently making a for loop to do this and a trim logic to get the exact desired length and not only multiples of the texture's width and height. But I think this is inefficient and that I'll get into performance troubles quite soon with tons of Draw calls produced by the for loops. I'm quite new to MonoGame so I may be wrong in my understanding.

As far as I know, it's advisable to use a single large Texture2D with all your sprites and then draw portions of it so you can send it once to the GPU (I think this is what a Texture Atlas is, I may be wrong). Also, I learned that for drawing a repeatable texture it would be better to just use a Sampler State that wraps around edges, such as SamplerState.PointWrap, to repeat such a texture using a single SpriteBatch.Draw call.

These two concepts led me to this "deadlock" because I think I cannot use this "wrap" technique if my Texture2D doesn't exclusively contain the repeatable sprite, which is the case when you have a Texture Atlas.

Is there a way to follow both of these best practices? If not, which should I prefer or what should I consider to choose between them? Or, of course, I can be completely missing the point and my assumptions don't hold.

PS1: I found this other SO question but I don't quite understand what they're discussing to know if it's useful for my problem.

PS2: I found this SO question too but I don't think I can use the suggested approach to solve my problem as well.


Solution

  • You have several possibilities to achieve that. Indeed, with a sprite atlas, it is somewhat more complicated.

    If you create the vertices yourself (e.g. with VertexPositionColorTexture), you could achieve the repetition of the texture from within an atlas by stringing together many quads, line for line, row for row. This is often done for tilemaps, where e.g. each chunk of tiles (one quad for each tile) is only recreated when something changed. The recreation cost (rebuilding the mesh and sending it to the GPU) is so low that it is often the fastest solution.

    Another way to achieve it, is to do the repetition/wrapping math inside of a custom shader which is a bit more involved. You could e.g. use the alpha value of the vertex color to send up to 256 different texture IDs and then calculate your texture coordinates to keep them inside the given size:

     float2 wrappedCoords = frac(input.texCoord) * tileSize + tilePosition;
    

    A third way to achieve this is via using texture arrays or even a 3D texture (if Point filtering is enough for your use case). Not sure how easy it is to set them up with MonoGame but the advantage is that the whole wrapping part is like with a standard texture. The third texCoord (z) is for choosing the correct texture inside the array. They all have the same dimensions though if that is a concern.

    Let me know if this helped or if you need more information!