Workaround for Texture2D.GetData

| | August 5, 2015

I’m converting a game from XNA to iOS with Monogame.
In the code snippet below, smallDeform is a Texture2D on which I call the GetData method.

smallDeform = Game.Content.Load<Texture2D>("Terrain/...");
smallDeform.GetData(smallDeformData, 0, smallDeform.Width * smallDeform.Height);

I’m having some problem with Monogame because the feature in iOS has not been implemented yet because it returns this exception.

#if IOS 
   throw new NotImplementedException();
#elif ANDROID

I tried to serialize the data in a XML file from Windows to load the whole file from iOS. The serialized files weight more than 100MB each, that is quite unacceptable to parse.

Basically, I’m looking for a workaround to get the data (for instance a uint[] or Color[]) from a texture without using the GetData method.

PS: I’m on Mac, so I can’t use the Monogame SharpDX library.

Thanks in advance.

2 Responses to “Workaround for Texture2D.GetData”

  1. craftworkgames on November 30, -0001 @ 12:00 AM

    Serializing the data is actually a pretty clever workaround but XML is far to heavy for the job. What I would do is use a custom made, but simple, binary format so that you can control the size of the output.

    Here are a couple of methods I whipped up that should work, but please note that I made them specifically to answer this question and they have not been tested.

    EDIT: pinckerman pointed out that using File.Open doesn’t work on all platforms so I’ve changed my answer slightly to use TitleContainer.OpenStream instead. As you can see from the MonoGame source code the TitleContainer is intended to handle reading files in a platform agnostic way with MonoGame. Again, this should work but I haven’t tested it. If the answer needs to be corrected after implementation please do so.

    NOTE: On most mobile devices only a small about of data can be written to isolated storage. This is obviously not sufficient or required for this task so I’ve left the File.Open in the SaveTextureData method. This should work on Windows 7 but if you are using WinRT you should do it pinckerman’s way.

    To save the data..

    private void SaveTextureData(Texture2D texture, string filename)
    {
        int width = texture.Width;
        int height = texture.Height;
        Color[] data = new Color[width * height];
        texture.GetData<Color>(data, 0, data.Length);
    
        using (var stream = File.Open(filename, FileMode.Create)) 
        using (var writer = new BinaryWriter(stream))
        {
            writer.Write(width);
            writer.Write(height);
            writer.Write(data.Length);
    
            for (int i = 0; i < data.Length; i++)
            {
                writer.Write(data[i].R);
                writer.Write(data[i].G);
                writer.Write(data[i].B);
                writer.Write(data[i].A);
            }
        }
    }
    

    And to load the data..

    private Texture2D LoadTextureData(string filename)
    {
        using (var stream = new TitleContainer.OpenStream(filename)) 
        using (var reader = new BinaryReader(stream))
        {                
            var width = reader.ReadInt32();
            var height = reader.ReadInt32();
            var length = reader.ReadInt32();
            var data = new Color[length];
    
            for (int i = 0; i < data.Length; i++)
            {
                var r = reader.ReadByte();
                var g = reader.ReadByte();
                var b = reader.ReadByte();
                var a = reader.ReadByte();
                data[i] = new Color(r, g, b, a);
            }
    
            var texture = new Texture2D(GraphicsDevice, width, height);
            texture.SetData<Color>(data, 0, data.Length);
            return texture;
        }
    }
    

    Binary data is far smaller than XML data. It’s about as small as you’re going to get without compression. Just be aware that binary formats are pretty rigid when it comes to change. If you need to change the format it’ll be easier in most cases to write out new files.

    If you need to make changes to the methods, be careful to keep them in sync. Every Write should be matched with an identical Read in the exact same order and data type.

    I’m interested to know how much smaller the files become. Let me know how it goes?

  2. I answer my own question, if someone will have the same problem.

    Following craftworkgame‘s suggestion, I saved my data using byte streams, but due to the absence of File class, I had to use WinRT functions:

    private async void SaveColorArray(string filename, Color[] array)
    {
        StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.ReplaceExisting);
        IRandomAccessStream writeStream = await sampleFile.OpenAsync(FileAccessMode.ReadWrite);
        IOutputStream outputSteam = writeStream.GetOutputStreamAt(0);
        DataWriter dataWriter = new DataWriter(outputSteam);
        for (int i = 0; i < array.Length; i++)
        {
            dataWriter.WriteByte(array[i].R);
            dataWriter.WriteByte(array[i].G);
            dataWriter.WriteByte(array[i].B);
            dataWriter.WriteByte(array[i].A);
        }
    
        await dataWriter.StoreAsync();
        await outputSteam.FlushAsync();
    }
    
    protected async Task<Color[]> LoadColorArray(string filename)
    {
        StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.OpenIfExists);
        IRandomAccessStream readStream = await sampleFile.OpenAsync(FileAccessMode.Read);
        IInputStream inputSteam = readStream.GetInputStreamAt(0);
        DataReader dataReader = new DataReader(inputSteam);
        await dataReader.LoadAsync((uint)readStream.Size);
    
        Color[] levelArray = new Color[dataReader.UnconsumedBufferLength / 4];
        int i = 0;
        while (dataReader.UnconsumedBufferLength > 0)
        {
            byte[] colorArray = new byte[4];
            dataReader.ReadBytes(colorArray);
            levelArray[i++] = new Color(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
        }
    
        return levelArray;
    }
    

Leave a Reply