How to create a texture2d-array in monogame?

Hello,

I know that one must create a single Texture2D-Object and specify the array size (of the texture array).
But how exactly can I add Textures to this Texture2D-Object (that represents the the texture array that is to be passed to the graphics card)?

I tried using the .SetData with T = Texture2D method, but - as some expected probably - it returns an error (T must be a non-nullable type).

Best regards!

The SetData function you have to use looks like this:

public void SetData<T>(int level, int arraySlice, Rectangle? rect, T[] data, int startIndex, int elementCount) where T : struct

The data parameter is not a texture, it’s an array of pixels. What the individual pixels look like depends on the surface format of the texture. For the standard Color format it is 4 bytes per pixel, which can also be packed into a single int or unsigned int per pixel.

Since you want to get another texture into the texture array, you can use Texture2D.GetData on that other texture to get the pixel array.

1 Like

This can also be used for sprite sheets, right? For specifying animation frames?

That’s possible, yes.

Hm, okay, makes sense.

I created following class:

public class TextureArray : Texture2D
{
    public TextureArray(GraphicsDevice graphicsDevice, int width, int height, int arraySize) : 
        base(graphicsDevice, width, height, true, SurfaceFormat.Color, SurfaceType.Texture, false, arraySize)
    {

    }
    public void Add(int index, Texture2D texture)
    {
        Color[] pixelData = new Color[texture.Width * texture.Height];
        texture.GetData<Color>(pixelData);
        this.SetData<Color>(0, index, null, pixelData, 0, pixelData.Length);
    }
   
}

I pass the two textures this way:

    mTexArray = new TextureArray(GlobalRessources.GraphicsDevice, 256, 256, 2);

        mTexArray.Add(0, mTex);
        mTexArray.Add(1, mTex1);

My hlsl-shader:

Texture2DArray Textures;
sampler sMainTextureSampler = sampler_state
{
	MipFilter = LINEAR;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	ADDRESSU = Wrap;
	ADDRESSV = Wrap;
};

float4 MainPS(VertexShaderOutput input) : COLOR
{
    
    float4 color = Textures.Sample(sMainTextureSampler, float3(input.TextureCoordinate, INDEX));
    return color;

}

It “works” - if I set INDEX to 0, it shows the first texture, if I set it to 1 it shows the other one… but only if
my camera is near enough to face, otherwise it fades out to black…:

Why?
Best regards.
_

Thank God, solved.

I had to use:
Textures.SampleLevel(sMainTextureSampler, float3(input.TextureCoordinate, INDEX), 0);

The texture turns black when the camera moves further away, because smaller mipmap levels are used automatically. Your texture doesn’t have any extra mipmap levels, so it turns black.

With Textures.SampleLevel you are now always forcing the mipmap level to 0. It works, but there’s the disadvantage that you are loosing mipmapping.

You should be able to also create the mipmaps by creating your textures with the mipmap parameter set to true. Then you can loop over all the mipmap levels and call GetData and SetData for each level individually. That’s what the level parameter is for in GetData / SetData.

1 Like

Thank God, I finally got it:

// - 1 -
    public class TextureArray : Texture2D
    {
        public TextureArray(GraphicsDevice graphicsDevice, int width, int height, int arraySize) : 
            base(graphicsDevice, width, height, true, SurfaceFormat.Color, SurfaceType.Texture, false, arraySize)
        {

        }
        public void Add(int index, Texture2D texture)
        {
            for (int i = 0; i < texture.LevelCount; i++)
            {

                float divisor = 1.0f / (1 << i);
                int[] pixelData = new int[(int)(texture.Width * texture.Height * divisor * divisor)];

                texture.GetData<int>(
                    i, 
                    0, 
                    new Rectangle(0, 0, (int)(texture.Width * divisor), (int)(texture.Height * divisor)), 
                    pixelData, 
                    0, 
                    pixelData.Length);

                this.SetData<int>(
                    i, 
                    index, 
                    new Rectangle(0, 0, (int)(texture.Width * divisor), (int)(texture.Height * divisor)),
                    pixelData, 
                    0, 
                    pixelData.Length);
            }
        }

        public static TextureArray LoadFromContentFolder(GraphicsDevice graphicsDevice, int widthPerTex, int heightPerTex, string path)
        {
            var paths = Directory.GetFiles(Environment.CurrentDirectory + @"\Content\" + path);

            TextureArray pTexArray = new TextureArray(graphicsDevice, widthPerTex, heightPerTex, paths.Length);

            int index = 0;

            foreach (var file in paths)
                pTexArray.Add(index++, GlobalRessources.Content.Load<Texture2D>(path + @"\" + Path.GetFileNameWithoutExtension(file)));

            return pTexArray;
        }
       
    }

Result:

_

2 Likes